/*
* Kendo UI v2014.3.1119 (http://www.telerik.com/kendo-ui)
* Copyright 2014 Telerik AD. All rights reserved.
*
* Kendo UI commercial licenses may be obtained at
* http://www.telerik.com/purchase/license-agreement/kendo-ui-complete
* If you do not own a commercial license, this file shall be governed by the trial license terms.
*/
(function (f, define) {
    define([], f);
})(function () {



    /*jshint eqnull: true, loopfunc: true, evil: true, boss: true, freeze: false*/
    (function ($, window, undefined) {
        var kendo = window.kendo = window.kendo || { cultures: {} },
        extend = $.extend,
        each = $.each,
        isArray = $.isArray,
        proxy = $.proxy,
        noop = $.noop,
        math = Math,
        Template,
        JSON = window.JSON || {},
        support = {},
        percentRegExp = /%/,
        formatRegExp = /\{(\d+)(:[^\}]+)?\}/g,
        boxShadowRegExp = /(\d+(?:\.?)\d*)px\s*(\d+(?:\.?)\d*)px\s*(\d+(?:\.?)\d*)px\s*(\d+)?/i,
        numberRegExp = /^(\+|-?)\d+(\.?)\d*$/,
        FUNCTION = "function",
        STRING = "string",
        NUMBER = "number",
        OBJECT = "object",
        NULL = "null",
        BOOLEAN = "boolean",
        UNDEFINED = "undefined",
        getterCache = {},
        setterCache = {},
        slice = [].slice,
        globalize = window.Globalize;

        kendo.version = "2014.3.1119";

        function Class() { }

        Class.extend = function (proto) {
            var base = function () { },
            member,
            that = this,
            subclass = proto && proto.init ? proto.init : function () {
                that.apply(this, arguments);
            },
            fn;

            base.prototype = that.prototype;
            fn = subclass.fn = subclass.prototype = new base();

            for (member in proto) {
                if (proto[member] != null && proto[member].constructor === Object) {
                    // Merge object members
                    fn[member] = extend(true, {}, base.prototype[member], proto[member]);
                } else {
                    fn[member] = proto[member];
                }
            }

            fn.constructor = subclass;
            subclass.extend = that.extend;

            return subclass;
        };

        Class.prototype._initOptions = function (options) {
            this.options = deepExtend({}, this.options, options);
        };

        var isFunction = kendo.isFunction = function (fn) {
            return typeof fn === "function";
        };

        var preventDefault = function () {
            this._defaultPrevented = true;
        };

        var isDefaultPrevented = function () {
            return this._defaultPrevented === true;
        };

        var Observable = Class.extend({
            init: function () {
                this._events = {};
            },

            bind: function (eventName, handlers, one) {
                var that = this,
                idx,
                eventNames = typeof eventName === STRING ? [eventName] : eventName,
                length,
                original,
                handler,
                handlersIsFunction = typeof handlers === FUNCTION,
                events;

                if (handlers === undefined) {
                    for (idx in eventName) {
                        that.bind(idx, eventName[idx]);
                    }
                    return that;
                }

                for (idx = 0, length = eventNames.length; idx < length; idx++) {
                    eventName = eventNames[idx];

                    handler = handlersIsFunction ? handlers : handlers[eventName];

                    if (handler) {
                        if (one) {
                            original = handler;
                            handler = function () {
                                that.unbind(eventName, handler);
                                original.apply(that, arguments);
                            };
                            handler.original = original;
                        }
                        events = that._events[eventName] = that._events[eventName] || [];
                        events.push(handler);
                    }
                }

                return that;
            },

            one: function (eventNames, handlers) {
                return this.bind(eventNames, handlers, true);
            },

            first: function (eventName, handlers) {
                var that = this,
                idx,
                eventNames = typeof eventName === STRING ? [eventName] : eventName,
                length,
                handler,
                handlersIsFunction = typeof handlers === FUNCTION,
                events;

                for (idx = 0, length = eventNames.length; idx < length; idx++) {
                    eventName = eventNames[idx];

                    handler = handlersIsFunction ? handlers : handlers[eventName];

                    if (handler) {
                        events = that._events[eventName] = that._events[eventName] || [];
                        events.unshift(handler);
                    }
                }

                return that;
            },

            trigger: function (eventName, e) {
                var that = this,
                events = that._events[eventName],
                idx,
                length;

                if (events) {
                    e = e || {};

                    e.sender = that;

                    e._defaultPrevented = false;

                    e.preventDefault = preventDefault;

                    e.isDefaultPrevented = isDefaultPrevented;

                    events = events.slice();

                    for (idx = 0, length = events.length; idx < length; idx++) {
                        events[idx].call(that, e);
                    }

                    return e._defaultPrevented === true;
                }

                return false;
            },

            unbind: function (eventName, handler) {
                var that = this,
                events = that._events[eventName],
                idx;

                if (eventName === undefined) {
                    that._events = {};
                } else if (events) {
                    if (handler) {
                        for (idx = events.length - 1; idx >= 0; idx--) {
                            if (events[idx] === handler || events[idx].original === handler) {
                                events.splice(idx, 1);
                            }
                        }
                    } else {
                        that._events[eventName] = [];
                    }
                }

                return that;
            }
        });


        function compilePart(part, stringPart) {
            if (stringPart) {
                return "'" +
                 part.split("'").join("\\'")
                     .split('\\"').join('\\\\\\"')
                     .replace(/\n/g, "\\n")
                     .replace(/\r/g, "\\r")
                     .replace(/\t/g, "\\t") + "'";
            } else {
                var first = part.charAt(0),
                 rest = part.substring(1);

                if (first === "=") {
                    return "+(" + rest + ")+";
                } else if (first === ":") {
                    return "+e(" + rest + ")+";
                } else {
                    return ";" + part + ";o+=";
                }
            }
        }

        var argumentNameRegExp = /^\w+/,
        encodeRegExp = /\$\{([^}]*)\}/g,
        escapedCurlyRegExp = /\\\}/g,
        curlyRegExp = /__CURLY__/g,
        escapedSharpRegExp = /\\#/g,
        sharpRegExp = /__SHARP__/g,
        zeros = ["", "0", "00", "000", "0000"];

        Template = {
            paramName: "data", // name of the parameter of the generated template
            useWithBlock: true, // whether to wrap the template in a with() block
            render: function (template, data) {
                var idx,
                length,
                html = "";

                for (idx = 0, length = data.length; idx < length; idx++) {
                    html += template(data[idx]);
                }

                return html;
            },
            compile: function (template, options) {
                var settings = extend({}, this, options),
                paramName = settings.paramName,
                argumentName = paramName.match(argumentNameRegExp)[0],
                useWithBlock = settings.useWithBlock,
                functionBody = "var o,e=kendo.htmlEncode;",
                fn,
                parts,
                idx;

                if (isFunction(template)) {
                    return template;
                }

                functionBody += useWithBlock ? "with(" + paramName + "){" : "";

                functionBody += "o=";

                parts = template
                .replace(escapedCurlyRegExp, "__CURLY__")
                .replace(encodeRegExp, "#=e($1)#")
                .replace(curlyRegExp, "}")
                .replace(escapedSharpRegExp, "__SHARP__")
                .split("#");

                for (idx = 0; idx < parts.length; idx++) {
                    functionBody += compilePart(parts[idx], idx % 2 === 0);
                }

                functionBody += useWithBlock ? ";}" : ";";

                functionBody += "return o;";

                functionBody = functionBody.replace(sharpRegExp, "#");

                try {
                    fn = new Function(argumentName, functionBody);
                    fn._slotCount = Math.floor(parts.length / 2);
                    return fn;
                } catch (e) {
                    throw new Error(kendo.format("Invalid template:'{0}' Generated code:'{1}'", template, functionBody));
                }
            }
        };

        function pad(number, digits, end) {
            number = number + "";
            digits = digits || 2;
            end = digits - number.length;

            if (end) {
                return zeros[digits].substring(0, end) + number;
            }

            return number;
        }

        //JSON stringify
        (function () {
            var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {
            "\b": "\\b",
            "\t": "\\t",
            "\n": "\\n",
            "\f": "\\f",
            "\r": "\\r",
            "\"": '\\"',
            "\\": "\\\\"
        },
        rep,
        toString = {}.toString;


            if (typeof Date.prototype.toJSON !== FUNCTION) {

                Date.prototype.toJSON = function () {
                    var that = this;

                    return isFinite(that.valueOf()) ?
                pad(that.getUTCFullYear(), 4) + "-" +
                pad(that.getUTCMonth() + 1) + "-" +
                pad(that.getUTCDate()) + "T" +
                pad(that.getUTCHours()) + ":" +
                pad(that.getUTCMinutes()) + ":" +
                pad(that.getUTCSeconds()) + "Z" : null;
                };

                String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function () {
                    return this.valueOf();
                };
            }

            function quote(string) {
                escapable.lastIndex = 0;
                return escapable.test(string) ? "\"" + string.replace(escapable, function (a) {
                    var c = meta[a];
                    return typeof c === STRING ? c :
                "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
                }) + "\"" : "\"" + string + "\"";
            }

            function str(key, holder) {
                var i,
            k,
            v,
            length,
            mind = gap,
            partial,
            value = holder[key],
            type;

                if (value && typeof value === OBJECT && typeof value.toJSON === FUNCTION) {
                    value = value.toJSON(key);
                }

                if (typeof rep === FUNCTION) {
                    value = rep.call(holder, key, value);
                }

                type = typeof value;
                if (type === STRING) {
                    return quote(value);
                } else if (type === NUMBER) {
                    return isFinite(value) ? String(value) : NULL;
                } else if (type === BOOLEAN || type === NULL) {
                    return String(value);
                } else if (type === OBJECT) {
                    if (!value) {
                        return NULL;
                    }
                    gap += indent;
                    partial = [];
                    if (toString.apply(value) === "[object Array]") {
                        length = value.length;
                        for (i = 0; i < length; i++) {
                            partial[i] = str(i, value) || NULL;
                        }
                        v = partial.length === 0 ? "[]" : gap ?
                    "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" :
                    "[" + partial.join(",") + "]";
                        gap = mind;
                        return v;
                    }
                    if (rep && typeof rep === OBJECT) {
                        length = rep.length;
                        for (i = 0; i < length; i++) {
                            if (typeof rep[i] === STRING) {
                                k = rep[i];
                                v = str(k, value);
                                if (v) {
                                    partial.push(quote(k) + (gap ? ": " : ":") + v);
                                }
                            }
                        }
                    } else {
                        for (k in value) {
                            if (Object.hasOwnProperty.call(value, k)) {
                                v = str(k, value);
                                if (v) {
                                    partial.push(quote(k) + (gap ? ": " : ":") + v);
                                }
                            }
                        }
                    }

                    v = partial.length === 0 ? "{}" : gap ?
                "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" :
                "{" + partial.join(",") + "}";
                    gap = mind;
                    return v;
                }
            }

            if (typeof JSON.stringify !== FUNCTION) {
                JSON.stringify = function (value, replacer, space) {
                    var i;
                    gap = "";
                    indent = "";

                    if (typeof space === NUMBER) {
                        for (i = 0; i < space; i += 1) {
                            indent += " ";
                        }

                    } else if (typeof space === STRING) {
                        indent = space;
                    }

                    rep = replacer;
                    if (replacer && typeof replacer !== FUNCTION && (typeof replacer !== OBJECT || typeof replacer.length !== NUMBER)) {
                        throw new Error("JSON.stringify");
                    }

                    return str("", { "": value });
                };
            }
        })();

        // Date and Number formatting
        (function () {
            var dateFormatRegExp = /dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|HH|H|hh|h|mm|m|fff|ff|f|tt|ss|s|zzz|zz|z|"[^"]*"|'[^']*'/g,
        standardFormatRegExp = /^(n|c|p|e)(\d*)$/i,
        literalRegExp = /(\\.)|(['][^']*[']?)|(["][^"]*["]?)/g,
        commaRegExp = /\,/g,
        EMPTY = "",
        POINT = ".",
        COMMA = ",",
        SHARP = "#",
        ZERO = "0",
        PLACEHOLDER = "??",
        EN = "en-US",
        objectToString = {}.toString;

            //cultures
            kendo.cultures["en-US"] = {
                name: EN,
                numberFormat: {
                    pattern: ["-n"],
                    decimals: 2,
                    ",": ",",
                    ".": ".",
                    groupSize: [3],
                    percent: {
                        pattern: ["-n %", "n %"],
                        decimals: 2,
                        ",": ",",
                        ".": ".",
                        groupSize: [3],
                        symbol: "%"
                    },
                    currency: {
                        pattern: ["($n)", "$n"],
                        decimals: 2,
                        ",": ",",
                        ".": ".",
                        groupSize: [3],
                        symbol: "$"
                    }
                },
                calendars: {
                    standard: {
                        days: {
                            names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
                            namesAbbr: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
                            namesShort: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
                        },
                        months: {
                            names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
                            namesAbbr: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
                        },
                        AM: ["AM", "am", "AM"],
                        PM: ["PM", "pm", "PM"],
                        patterns: {
                            d: "M/d/yyyy",
                            D: "dddd, MMMM dd, yyyy",
                            F: "dddd, MMMM dd, yyyy h:mm:ss tt",
                            g: "M/d/yyyy h:mm tt",
                            G: "M/d/yyyy h:mm:ss tt",
                            m: "MMMM dd",
                            M: "MMMM dd",
                            s: "yyyy'-'MM'-'ddTHH':'mm':'ss",
                            t: "h:mm tt",
                            T: "h:mm:ss tt",
                            u: "yyyy'-'MM'-'dd HH':'mm':'ss'Z'",
                            y: "MMMM, yyyy",
                            Y: "MMMM, yyyy"
                        },
                        "/": "/",
                        ":": ":",
                        firstDay: 0,
                        twoDigitYearMax: 2029
                    }
                }
            };


            function findCulture(culture) {
                if (culture) {
                    if (culture.numberFormat) {
                        return culture;
                    }

                    if (typeof culture === STRING) {
                        var cultures = kendo.cultures;
                        return cultures[culture] || cultures[culture.split("-")[0]] || null;
                    }

                    return null;
                }

                return null;
            }

            function getCulture(culture) {
                if (culture) {
                    culture = findCulture(culture);
                }

                return culture || kendo.cultures.current;
            }

            function expandNumberFormat(numberFormat) {
                numberFormat.groupSizes = numberFormat.groupSize;
                numberFormat.percent.groupSizes = numberFormat.percent.groupSize;
                numberFormat.currency.groupSizes = numberFormat.currency.groupSize;
            }

            kendo.culture = function (cultureName) {
                var cultures = kendo.cultures, culture;

                if (cultureName !== undefined) {
                    culture = findCulture(cultureName) || cultures[EN];
                    culture.calendar = culture.calendars.standard;
                    cultures.current = culture;

                    if (globalize && !globalize.load) {
                        expandNumberFormat(culture.numberFormat);
                    }

                } else {
                    return cultures.current;
                }
            };

            kendo.findCulture = findCulture;
            kendo.getCulture = getCulture;

            //set current culture to en-US.
            kendo.culture(EN);

            function formatDate(date, format, culture) {
                culture = getCulture(culture);

                var calendar = culture.calendars.standard,
            days = calendar.days,
            months = calendar.months;

                format = calendar.patterns[format] || format;

                return format.replace(dateFormatRegExp, function (match) {
                    var minutes;
                    var result;
                    var sign;

                    if (match === "d") {
                        result = date.getDate();
                    } else if (match === "dd") {
                        result = pad(date.getDate());
                    } else if (match === "ddd") {
                        result = days.namesAbbr[date.getDay()];
                    } else if (match === "dddd") {
                        result = days.names[date.getDay()];
                    } else if (match === "M") {
                        result = date.getMonth() + 1;
                    } else if (match === "MM") {
                        result = pad(date.getMonth() + 1);
                    } else if (match === "MMM") {
                        result = months.namesAbbr[date.getMonth()];
                    } else if (match === "MMMM") {
                        result = months.names[date.getMonth()];
                    } else if (match === "yy") {
                        result = pad(date.getFullYear() % 100);
                    } else if (match === "yyyy") {
                        result = pad(date.getFullYear(), 4);
                    } else if (match === "h") {
                        result = date.getHours() % 12 || 12;
                    } else if (match === "hh") {
                        result = pad(date.getHours() % 12 || 12);
                    } else if (match === "H") {
                        result = date.getHours();
                    } else if (match === "HH") {
                        result = pad(date.getHours());
                    } else if (match === "m") {
                        result = date.getMinutes();
                    } else if (match === "mm") {
                        result = pad(date.getMinutes());
                    } else if (match === "s") {
                        result = date.getSeconds();
                    } else if (match === "ss") {
                        result = pad(date.getSeconds());
                    } else if (match === "f") {
                        result = math.floor(date.getMilliseconds() / 100);
                    } else if (match === "ff") {
                        result = date.getMilliseconds();
                        if (result > 99) {
                            result = math.floor(result / 10);
                        }
                        result = pad(result);
                    } else if (match === "fff") {
                        result = pad(date.getMilliseconds(), 3);
                    } else if (match === "tt") {
                        result = date.getHours() < 12 ? calendar.AM[0] : calendar.PM[0];
                    } else if (match === "zzz") {
                        minutes = date.getTimezoneOffset();
                        sign = minutes < 0;

                        result = math.abs(minutes / 60).toString().split(".")[0];
                        minutes = math.abs(minutes) - (result * 60);

                        result = (sign ? "+" : "-") + pad(result);
                        result += ":" + pad(minutes);
                    } else if (match === "zz" || match === "z") {
                        result = date.getTimezoneOffset() / 60;
                        sign = result < 0;

                        result = math.abs(result).toString().split(".")[0];
                        result = (sign ? "+" : "-") + (match === "zz" ? pad(result) : result);
                    }

                    return result !== undefined ? result : match.slice(1, match.length - 1);
                });
            }

            //number formatting
            function formatNumber(number, format, culture) {
                culture = getCulture(culture);

                var numberFormat = culture.numberFormat,
            groupSize = numberFormat.groupSize[0],
            groupSeparator = numberFormat[COMMA],
            decimal = numberFormat[POINT],
            precision = numberFormat.decimals,
            pattern = numberFormat.pattern[0],
            literals = [],
            symbol,
            isCurrency, isPercent,
            customPrecision,
            formatAndPrecision,
            negative = number < 0,
            integer,
            fraction,
            integerLength,
            fractionLength,
            replacement = EMPTY,
            value = EMPTY,
            idx,
            length,
            ch,
            hasGroup,
            hasNegativeFormat,
            decimalIndex,
            sharpIndex,
            zeroIndex,
            hasZero, hasSharp,
            percentIndex,
            currencyIndex,
            startZeroIndex,
            start = -1,
            end;

                //return empty string if no number
                if (number === undefined) {
                    return EMPTY;
                }

                if (!isFinite(number)) {
                    return number;
                }

                //if no format then return number.toString() or number.toLocaleString() if culture.name is not defined
                if (!format) {
                    return culture.name.length ? number.toLocaleString() : number.toString();
                }

                formatAndPrecision = standardFormatRegExp.exec(format);

                // standard formatting
                if (formatAndPrecision) {
                    format = formatAndPrecision[1].toLowerCase();

                    isCurrency = format === "c";
                    isPercent = format === "p";

                    if (isCurrency || isPercent) {
                        //get specific number format information if format is currency or percent
                        numberFormat = isCurrency ? numberFormat.currency : numberFormat.percent;
                        groupSize = numberFormat.groupSize[0];
                        groupSeparator = numberFormat[COMMA];
                        decimal = numberFormat[POINT];
                        precision = numberFormat.decimals;
                        symbol = numberFormat.symbol;
                        pattern = numberFormat.pattern[negative ? 0 : 1];
                    }

                    customPrecision = formatAndPrecision[2];

                    if (customPrecision) {
                        precision = +customPrecision;
                    }

                    //return number in exponential format
                    if (format === "e") {
                        return customPrecision ? number.toExponential(precision) : number.toExponential(); // toExponential() and toExponential(undefined) differ in FF #653438.
                    }

                    // multiply if format is percent
                    if (isPercent) {
                        number *= 100;
                    }

                    number = round(number, precision);
                    negative = number < 0;
                    number = number.split(POINT);

                    integer = number[0];
                    fraction = number[1];

                    //exclude "-" if number is negative.
                    if (negative) {
                        integer = integer.substring(1);
                    }

                    value = integer;
                    integerLength = integer.length;

                    //add group separator to the number if it is longer enough
                    if (integerLength >= groupSize) {
                        value = EMPTY;
                        for (idx = 0; idx < integerLength; idx++) {
                            if (idx > 0 && (integerLength - idx) % groupSize === 0) {
                                value += groupSeparator;
                            }
                            value += integer.charAt(idx);
                        }
                    }

                    if (fraction) {
                        value += decimal + fraction;
                    }

                    if (format === "n" && !negative) {
                        return value;
                    }

                    number = EMPTY;

                    for (idx = 0, length = pattern.length; idx < length; idx++) {
                        ch = pattern.charAt(idx);

                        if (ch === "n") {
                            number += value;
                        } else if (ch === "$" || ch === "%") {
                            number += symbol;
                        } else {
                            number += ch;
                        }
                    }

                    return number;
                }

                //custom formatting
                //
                //separate format by sections.

                //make number positive
                if (negative) {
                    number = -number;
                }

                if (format.indexOf("'") > -1 || format.indexOf("\"") > -1 || format.indexOf("\\") > -1) {
                    format = format.replace(literalRegExp, function (match) {
                        var quoteChar = match.charAt(0).replace("\\", ""),
                    literal = match.slice(1).replace(quoteChar, "");

                        literals.push(literal);

                        return PLACEHOLDER;
                    });
                }

                format = format.split(";");
                if (negative && format[1]) {
                    //get negative format
                    format = format[1];
                    hasNegativeFormat = true;
                } else if (number === 0) {
                    //format for zeros
                    format = format[2] || format[0];
                    if (format.indexOf(SHARP) == -1 && format.indexOf(ZERO) == -1) {
                        //return format if it is string constant.
                        return format;
                    }
                } else {
                    format = format[0];
                }

                percentIndex = format.indexOf("%");
                currencyIndex = format.indexOf("$");

                isPercent = percentIndex != -1;
                isCurrency = currencyIndex != -1;

                //multiply number if the format has percent
                if (isPercent) {
                    number *= 100;
                }

                if (isCurrency && format[currencyIndex - 1] === "\\") {
                    format = format.split("\\").join("");
                    isCurrency = false;
                }

                if (isCurrency || isPercent) {
                    //get specific number format information if format is currency or percent
                    numberFormat = isCurrency ? numberFormat.currency : numberFormat.percent;
                    groupSize = numberFormat.groupSize[0];
                    groupSeparator = numberFormat[COMMA];
                    decimal = numberFormat[POINT];
                    precision = numberFormat.decimals;
                    symbol = numberFormat.symbol;
                }

                hasGroup = format.indexOf(COMMA) > -1;
                if (hasGroup) {
                    format = format.replace(commaRegExp, EMPTY);
                }

                decimalIndex = format.indexOf(POINT);
                length = format.length;

                if (decimalIndex != -1) {
                    fraction = number.toString().split("e");
                    if (fraction[1]) {
                        fraction = round(number, Math.abs(fraction[1]));
                    } else {
                        fraction = fraction[0];
                    }
                    fraction = fraction.split(POINT)[1] || EMPTY;
                    zeroIndex = format.lastIndexOf(ZERO) - decimalIndex;
                    sharpIndex = format.lastIndexOf(SHARP) - decimalIndex;
                    hasZero = zeroIndex > -1;
                    hasSharp = sharpIndex > -1;
                    idx = fraction.length;

                    if (!hasZero && !hasSharp) {
                        format = format.substring(0, decimalIndex) + format.substring(decimalIndex + 1);
                        length = format.length;
                        decimalIndex = -1;
                        idx = 0;
                    } if (hasZero && zeroIndex > sharpIndex) {
                        idx = zeroIndex;
                    } else if (sharpIndex > zeroIndex) {
                        if (hasSharp && idx > sharpIndex) {
                            idx = sharpIndex;
                        } else if (hasZero && idx < zeroIndex) {
                            idx = zeroIndex;
                        }
                    }

                    if (idx > -1) {
                        number = round(number, idx);
                    }
                } else {
                    number = round(number);
                }

                sharpIndex = format.indexOf(SHARP);
                startZeroIndex = zeroIndex = format.indexOf(ZERO);

                //define the index of the first digit placeholder
                if (sharpIndex == -1 && zeroIndex != -1) {
                    start = zeroIndex;
                } else if (sharpIndex != -1 && zeroIndex == -1) {
                    start = sharpIndex;
                } else {
                    start = sharpIndex > zeroIndex ? zeroIndex : sharpIndex;
                }

                sharpIndex = format.lastIndexOf(SHARP);
                zeroIndex = format.lastIndexOf(ZERO);

                //define the index of the last digit placeholder
                if (sharpIndex == -1 && zeroIndex != -1) {
                    end = zeroIndex;
                } else if (sharpIndex != -1 && zeroIndex == -1) {
                    end = sharpIndex;
                } else {
                    end = sharpIndex > zeroIndex ? sharpIndex : zeroIndex;
                }

                if (start == length) {
                    end = start;
                }

                if (start != -1) {
                    value = number.toString().split(POINT);
                    integer = value[0];
                    fraction = value[1] || EMPTY;

                    integerLength = integer.length;
                    fractionLength = fraction.length;

                    if (negative && (number * -1) >= 0) {
                        negative = false;
                    }

                    //add group separator to the number if it is longer enough
                    if (hasGroup) {
                        if (integerLength === groupSize && integerLength < decimalIndex - startZeroIndex) {
                            integer = groupSeparator + integer;
                        } else if (integerLength > groupSize) {
                            value = EMPTY;
                            for (idx = 0; idx < integerLength; idx++) {
                                if (idx > 0 && (integerLength - idx) % groupSize === 0) {
                                    value += groupSeparator;
                                }
                                value += integer.charAt(idx);
                            }

                            integer = value;
                        }
                    }

                    number = format.substring(0, start);

                    if (negative && !hasNegativeFormat) {
                        number += "-";
                    }

                    for (idx = start; idx < length; idx++) {
                        ch = format.charAt(idx);

                        if (decimalIndex == -1) {
                            if (end - idx < integerLength) {
                                number += integer;
                                break;
                            }
                        } else {
                            if (zeroIndex != -1 && zeroIndex < idx) {
                                replacement = EMPTY;
                            }

                            if ((decimalIndex - idx) <= integerLength && decimalIndex - idx > -1) {
                                number += integer;
                                idx = decimalIndex;
                            }

                            if (decimalIndex === idx) {
                                number += (fraction ? decimal : EMPTY) + fraction;
                                idx += end - decimalIndex + 1;
                                continue;
                            }
                        }

                        if (ch === ZERO) {
                            number += ch;
                            replacement = ch;
                        } else if (ch === SHARP) {
                            number += replacement;
                        }
                    }

                    if (end >= start) {
                        number += format.substring(end + 1);
                    }

                    //replace symbol placeholders
                    if (isCurrency || isPercent) {
                        value = EMPTY;
                        for (idx = 0, length = number.length; idx < length; idx++) {
                            ch = number.charAt(idx);
                            value += (ch === "$" || ch === "%") ? symbol : ch;
                        }
                        number = value;
                    }

                    length = literals.length;

                    if (length) {
                        for (idx = 0; idx < length; idx++) {
                            number = number.replace(PLACEHOLDER, literals[idx]);
                        }
                    }
                }

                return number;
            }

            var round = function (value, precision) {
                precision = precision || 0;

                value = value.toString().split('e');
                value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + precision) : precision)));

                value = value.toString().split('e');
                value = +(value[0] + 'e' + (value[1] ? (+value[1] - precision) : -precision));

                return value.toFixed(precision);
            };

            var toString = function (value, fmt, culture) {
                if (fmt) {
                    if (objectToString.call(value) === "[object Date]") {
                        return formatDate(value, fmt, culture);
                    } else if (typeof value === NUMBER) {
                        return formatNumber(value, fmt, culture);
                    }
                }

                return value !== undefined ? value : "";
            };

            if (globalize && !globalize.load) {
                toString = function (value, format, culture) {
                    if ($.isPlainObject(culture)) {
                        culture = culture.name;
                    }

                    return globalize.format(value, format, culture);
                };
            }

            kendo.format = function (fmt) {
                var values = arguments;

                return fmt.replace(formatRegExp, function (match, index, placeholderFormat) {
                    var value = values[parseInt(index, 10) + 1];

                    return toString(value, placeholderFormat ? placeholderFormat.substring(1) : "");
                });
            };

            kendo._extractFormat = function (format) {
                if (format.slice(0, 3) === "{0:") {
                    format = format.slice(3, format.length - 1);
                }

                return format;
            };

            kendo._activeElement = function () {
                try {
                    return document.activeElement;
                } catch (e) {
                    return document.documentElement.activeElement;
                }
            };

            kendo._round = round;
            kendo.toString = toString;
        })();


        (function () {
            var nonBreakingSpaceRegExp = /\u00A0/g,
        exponentRegExp = /[eE][\-+]?[0-9]+/,
        shortTimeZoneRegExp = /[+|\-]\d{1,2}/,
        longTimeZoneRegExp = /[+|\-]\d{1,2}:?\d{2}/,
        dateRegExp = /^\/Date\((.*?)\)\/$/,
        offsetRegExp = /[+-]\d*/,
        formatsSequence = ["G", "g", "d", "F", "D", "y", "m", "T", "t"],
        numberRegExp = {
            2: /^\d{1,2}/,
            3: /^\d{1,3}/,
            4: /^\d{4}/
        },
        objectToString = {}.toString;

            function outOfRange(value, start, end) {
                return !(value >= start && value <= end);
            }

            function designatorPredicate(designator) {
                return designator.charAt(0);
            }

            function mapDesignators(designators) {
                return $.map(designators, designatorPredicate);
            }

            //if date's day is different than the typed one - adjust
            function adjustDST(date, hours) {
                if (!hours && date.getHours() === 23) {
                    date.setHours(date.getHours() + 2);
                }
            }

            function lowerArray(data) {
                var idx = 0,
            length = data.length,
            array = [];

                for (; idx < length; idx++) {
                    array[idx] = (data[idx] + "").toLowerCase();
                }

                return array;
            }

            function lowerLocalInfo(localInfo) {
                var newLocalInfo = {}, property;

                for (property in localInfo) {
                    newLocalInfo[property] = lowerArray(localInfo[property]);
                }

                return newLocalInfo;
            }

            function parseExact(value, format, culture) {
                if (!value) {
                    return null;
                }

                var lookAhead = function (match) {
                    var i = 0;
                    while (format[idx] === match) {
                        i++;
                        idx++;
                    }
                    if (i > 0) {
                        idx -= 1;
                    }
                    return i;
                },
            getNumber = function (size) {
                var rg = numberRegExp[size] || new RegExp('^\\d{1,' + size + '}'),
                    match = value.substr(valueIdx, size).match(rg);

                if (match) {
                    match = match[0];
                    valueIdx += match.length;
                    return parseInt(match, 10);
                }
                return null;
            },
            getIndexByName = function (names, lower) {
                var i = 0,
                    length = names.length,
                    name, nameLength,
                    subValue;

                for (; i < length; i++) {
                    name = names[i];
                    nameLength = name.length;
                    subValue = value.substr(valueIdx, nameLength);

                    if (lower) {
                        subValue = subValue.toLowerCase();
                    }

                    if (subValue == name) {
                        valueIdx += nameLength;
                        return i + 1;
                    }
                }
                return null;
            },
            checkLiteral = function () {
                var result = false;
                if (value.charAt(valueIdx) === format[idx]) {
                    valueIdx++;
                    result = true;
                }
                return result;
            },
            calendar = culture.calendars.standard,
            year = null,
            month = null,
            day = null,
            hours = null,
            minutes = null,
            seconds = null,
            milliseconds = null,
            idx = 0,
            valueIdx = 0,
            literal = false,
            date = new Date(),
            twoDigitYearMax = calendar.twoDigitYearMax || 2029,
            defaultYear = date.getFullYear(),
            ch, count, length, pattern,
            pmHour, UTC, matches,
            amDesignators, pmDesignators,
            hoursOffset, minutesOffset,
            hasTime, match;

                if (!format) {
                    format = "d"; //shord date format
                }

                //if format is part of the patterns get real format
                pattern = calendar.patterns[format];
                if (pattern) {
                    format = pattern;
                }

                format = format.split("");
                length = format.length;

                for (; idx < length; idx++) {
                    ch = format[idx];

                    if (literal) {
                        if (ch === "'") {
                            literal = false;
                        } else {
                            checkLiteral();
                        }
                    } else {
                        if (ch === "d") {
                            count = lookAhead("d");
                            if (!calendar._lowerDays) {
                                calendar._lowerDays = lowerLocalInfo(calendar.days);
                            }

                            day = count < 3 ? getNumber(2) : getIndexByName(calendar._lowerDays[count == 3 ? "namesAbbr" : "names"], true);

                            if (day === null || outOfRange(day, 1, 31)) {
                                return null;
                            }
                        } else if (ch === "M") {
                            count = lookAhead("M");
                            if (!calendar._lowerMonths) {
                                calendar._lowerMonths = lowerLocalInfo(calendar.months);
                            }
                            month = count < 3 ? getNumber(2) : getIndexByName(calendar._lowerMonths[count == 3 ? 'namesAbbr' : 'names'], true);

                            if (month === null || outOfRange(month, 1, 12)) {
                                return null;
                            }
                            month -= 1; //because month is zero based
                        } else if (ch === "y") {
                            count = lookAhead("y");
                            year = getNumber(count);

                            if (year === null) {
                                return null;
                            }

                            if (count == 2) {
                                if (typeof twoDigitYearMax === "string") {
                                    twoDigitYearMax = defaultYear + parseInt(twoDigitYearMax, 10);
                                }

                                year = (defaultYear - defaultYear % 100) + year;
                                if (year > twoDigitYearMax) {
                                    year -= 100;
                                }
                            }
                        } else if (ch === "h") {
                            lookAhead("h");
                            hours = getNumber(2);
                            if (hours == 12) {
                                hours = 0;
                            }
                            if (hours === null || outOfRange(hours, 0, 11)) {
                                return null;
                            }
                        } else if (ch === "H") {
                            lookAhead("H");
                            hours = getNumber(2);
                            if (hours === null || outOfRange(hours, 0, 23)) {
                                return null;
                            }
                        } else if (ch === "m") {
                            lookAhead("m");
                            minutes = getNumber(2);
                            if (minutes === null || outOfRange(minutes, 0, 59)) {
                                return null;
                            }
                        } else if (ch === "s") {
                            lookAhead("s");
                            seconds = getNumber(2);
                            if (seconds === null || outOfRange(seconds, 0, 59)) {
                                return null;
                            }
                        } else if (ch === "f") {
                            count = lookAhead("f");

                            match = value.substr(valueIdx, count).match(numberRegExp[3]);
                            milliseconds = getNumber(count);

                            if (milliseconds !== null) {
                                match = match[0].length;

                                if (match < 3) {
                                    milliseconds *= Math.pow(10, (3 - match));
                                }

                                if (count > 3) {
                                    milliseconds = parseInt(milliseconds.toString().substring(0, 3), 10);
                                }
                            }

                            if (milliseconds === null || outOfRange(milliseconds, 0, 999)) {
                                return null;
                            }

                        } else if (ch === "t") {
                            count = lookAhead("t");
                            amDesignators = calendar.AM;
                            pmDesignators = calendar.PM;

                            if (count === 1) {
                                amDesignators = mapDesignators(amDesignators);
                                pmDesignators = mapDesignators(pmDesignators);
                            }

                            pmHour = getIndexByName(pmDesignators);
                            if (!pmHour && !getIndexByName(amDesignators)) {
                                return null;
                            }
                        }
                        else if (ch === "z") {
                            UTC = true;
                            count = lookAhead("z");

                            if (value.substr(valueIdx, 1) === "Z") {
                                checkLiteral();
                                continue;
                            }

                            matches = value.substr(valueIdx, 6)
                                   .match(count > 2 ? longTimeZoneRegExp : shortTimeZoneRegExp);

                            if (!matches) {
                                return null;
                            }

                            matches = matches[0].split(":");

                            hoursOffset = matches[0];
                            minutesOffset = matches[1];

                            if (!minutesOffset && hoursOffset.length > 3) { //(+|-)[hh][mm] format is used
                                valueIdx = hoursOffset.length - 2;
                                minutesOffset = hoursOffset.substring(valueIdx);
                                hoursOffset = hoursOffset.substring(0, valueIdx);
                            }

                            hoursOffset = parseInt(hoursOffset, 10);
                            if (outOfRange(hoursOffset, -12, 13)) {
                                return null;
                            }

                            if (count > 2) {
                                minutesOffset = parseInt(minutesOffset, 10);
                                if (isNaN(minutesOffset) || outOfRange(minutesOffset, 0, 59)) {
                                    return null;
                                }
                            }
                        } else if (ch === "'") {
                            literal = true;
                            checkLiteral();
                        } else if (!checkLiteral()) {
                            return null;
                        }
                    }
                }

                hasTime = hours !== null || minutes !== null || seconds || null;

                if (year === null && month === null && day === null && hasTime) {
                    year = defaultYear;
                    month = date.getMonth();
                    day = date.getDate();
                } else {
                    if (year === null) {
                        year = defaultYear;
                    }

                    if (day === null) {
                        day = 1;
                    }
                }

                if (pmHour && hours < 12) {
                    hours += 12;
                }

                if (UTC) {
                    if (hoursOffset) {
                        hours += -hoursOffset;
                    }

                    if (minutesOffset) {
                        minutes += -minutesOffset;
                    }

                    value = new Date(Date.UTC(year, month, day, hours, minutes, seconds, milliseconds));
                } else {
                    value = new Date(year, month, day, hours, minutes, seconds, milliseconds);
                    adjustDST(value, hours);
                }

                if (year < 100) {
                    value.setFullYear(year);
                }

                if (value.getDate() !== day && UTC === undefined) {
                    return null;
                }

                return value;
            }

            function parseMicrosoftFormatOffset(offset) {
                var sign = offset.substr(0, 1) === "-" ? -1 : 1;

                offset = offset.substring(1);
                offset = (parseInt(offset.substr(0, 2), 10) * 60) + parseInt(offset.substring(2), 10);

                return sign * offset;
            }

            kendo.parseDate = function (value, formats, culture) {
                if (objectToString.call(value) === "[object Date]") {
                    return value;
                }

                var idx = 0;
                var date = null;
                var length, patterns;
                var tzoffset;
                var sign;

                if (value && value.indexOf("/D") === 0) {
                    date = dateRegExp.exec(value);
                    if (date) {
                        date = date[1];
                        tzoffset = offsetRegExp.exec(date.substring(1));

                        date = new Date(parseInt(date, 10));

                        if (tzoffset) {
                            tzoffset = parseMicrosoftFormatOffset(tzoffset[0]);
                            date = kendo.timezone.apply(date, 0);
                            date = kendo.timezone.convert(date, 0, -1 * tzoffset);
                        }

                        return date;
                    }
                }

                culture = kendo.getCulture(culture);

                if (!formats) {
                    formats = [];
                    patterns = culture.calendar.patterns;
                    length = formatsSequence.length;

                    for (; idx < length; idx++) {
                        formats[idx] = patterns[formatsSequence[idx]];
                    }

                    idx = 0;

                    formats = [
                "yyyy/MM/dd HH:mm:ss",
                "yyyy/MM/dd HH:mm",
                "yyyy/MM/dd",
                "ddd MMM dd yyyy HH:mm:ss",
                "yyyy-MM-ddTHH:mm:ss.fffffffzzz",
                "yyyy-MM-ddTHH:mm:ss.fffzzz",
                "yyyy-MM-ddTHH:mm:sszzz",
                "yyyy-MM-ddTHH:mm:ss.fffffff",
                "yyyy-MM-ddTHH:mm:ss.fff",
                "yyyy-MM-ddTHH:mmzzz",
                "yyyy-MM-ddTHH:mmzz",
                "yyyy-MM-ddTHH:mm:ss",
                "yyyy-MM-ddTHH:mm",
                "yyyy-MM-dd HH:mm:ss",
                "yyyy-MM-dd HH:mm",
                "yyyy-MM-dd",
                "HH:mm:ss",
                "HH:mm"
            ].concat(formats);
                }

                formats = isArray(formats) ? formats : [formats];
                length = formats.length;

                for (; idx < length; idx++) {
                    date = parseExact(value, formats[idx], culture);
                    if (date) {
                        return date;
                    }
                }

                return date;
            };

            kendo.parseInt = function (value, culture) {
                var result = kendo.parseFloat(value, culture);
                if (result) {
                    result = result | 0;
                }
                return result;
            };

            kendo.parseFloat = function (value, culture, format) {
                if (!value && value !== 0) {
                    return null;
                }

                if (typeof value === NUMBER) {
                    return value;
                }

                value = value.toString();
                culture = kendo.getCulture(culture);

                var number = culture.numberFormat,
            percent = number.percent,
            currency = number.currency,
            symbol = currency.symbol,
            percentSymbol = percent.symbol,
            negative = value.indexOf("-"),
            parts, isPercent;

                //handle exponential number
                if (exponentRegExp.test(value)) {
                    value = parseFloat(value.replace(number["."], "."));
                    if (isNaN(value)) {
                        value = null;
                    }
                    return value;
                }

                if (negative > 0) {
                    return null;
                } else {
                    negative = negative > -1;
                }

                if (value.indexOf(symbol) > -1 || (format && format.toLowerCase().indexOf("c") > -1)) {
                    number = currency;
                    parts = number.pattern[0].replace("$", symbol).split("n");
                    if (value.indexOf(parts[0]) > -1 && value.indexOf(parts[1]) > -1) {
                        value = value.replace(parts[0], "").replace(parts[1], "");
                        negative = true;
                    }
                } else if (value.indexOf(percentSymbol) > -1) {
                    isPercent = true;
                    number = percent;
                    symbol = percentSymbol;
                }

                value = value.replace("-", "")
                     .replace(symbol, "")
                     .replace(nonBreakingSpaceRegExp, " ")
                     .split(number[","].replace(nonBreakingSpaceRegExp, " ")).join("")
                     .replace(number["."], ".");

                value = parseFloat(value);

                if (isNaN(value)) {
                    value = null;
                } else if (negative) {
                    value *= -1;
                }

                if (value && isPercent) {
                    value /= 100;
                }

                return value;
            };

            if (globalize && !globalize.load) {
                kendo.parseDate = function (value, format, culture) {
                    if (objectToString.call(value) === "[object Date]") {
                        return value;
                    }

                    return globalize.parseDate(value, format, culture);
                };

                kendo.parseFloat = function (value, culture) {
                    if (typeof value === NUMBER) {
                        return value;
                    }

                    if (value === undefined || value === null) {
                        return null;
                    }

                    if ($.isPlainObject(culture)) {
                        culture = culture.name;
                    }

                    value = globalize.parseFloat(value, culture);

                    return isNaN(value) ? null : value;
                };
            }
        })();

        function getShadows(element) {
            var shadow = element.css(kendo.support.transitions.css + "box-shadow") || element.css("box-shadow"),
            radius = shadow ? shadow.match(boxShadowRegExp) || [0, 0, 0, 0, 0] : [0, 0, 0, 0, 0],
            blur = math.max((+radius[3]), +(radius[4] || 0));

            return {
                left: (-radius[1]) + blur,
                right: (+radius[1]) + blur,
                bottom: (+radius[2]) + blur
            };
        }

        function wrap(element, autosize) {
            var browser = support.browser,
            percentage,
            isRtl = element.css("direction") == "rtl";

            if (!element.parent().hasClass("k-animation-container")) {
                var shadows = getShadows(element),
                width = element[0].style.width,
                height = element[0].style.height,
                percentWidth = percentRegExp.test(width),
                percentHeight = percentRegExp.test(height);

                if (browser.opera) { // Box shadow can't be retrieved in Opera
                    shadows.left = shadows.right = shadows.bottom = 5;
                }

                percentage = percentWidth || percentHeight;

                if (!percentWidth && (!autosize || (autosize && width))) { width = element.outerWidth(); }
                if (!percentHeight && (!autosize || (autosize && height))) { height = element.outerHeight(); }

                element.wrap(
                         $("<div/>")
                         .addClass("k-animation-container")
                         .css({
                             width: width,
                             height: height,
                             marginLeft: shadows.left * (isRtl ? 1 : -1),
                             paddingLeft: shadows.left,
                             paddingRight: shadows.right,
                             paddingBottom: shadows.bottom
                         }));

                if (percentage) {
                    element.css({
                        width: "100%",
                        height: "100%",
                        boxSizing: "border-box",
                        mozBoxSizing: "border-box",
                        webkitBoxSizing: "border-box"
                    });
                }
            } else {
                var wrapper = element.parent(".k-animation-container"),
                wrapperStyle = wrapper[0].style;

                if (wrapper.is(":hidden")) {
                    wrapper.show();
                }

                percentage = percentRegExp.test(wrapperStyle.width) || percentRegExp.test(wrapperStyle.height);

                if (!percentage) {
                    wrapper.css({
                        width: element.outerWidth(),
                        height: element.outerHeight(),
                        boxSizing: "content-box",
                        mozBoxSizing: "content-box",
                        webkitBoxSizing: "content-box"
                    });
                }
            }

            if (browser.msie && math.floor(browser.version) <= 7) {
                element.css({ zoom: 1 });
                element.children(".k-menu").width(element.width());
            }

            return element.parent();
        }

        function deepExtend(destination) {
            var i = 1,
            length = arguments.length;

            for (i = 1; i < length; i++) {
                deepExtendOne(destination, arguments[i]);
            }

            return destination;
        }

        function deepExtendOne(destination, source) {
            var ObservableArray = kendo.data.ObservableArray,
            LazyObservableArray = kendo.data.LazyObservableArray,
            DataSource = kendo.data.DataSource,
            HierarchicalDataSource = kendo.data.HierarchicalDataSource,
            property,
            propValue,
            propType,
            propInit,
            destProp;

            for (property in source) {
                propValue = source[property];
                propType = typeof propValue;

                if (propType === OBJECT && propValue !== null) {
                    propInit = propValue.constructor;
                } else {
                    propInit = null;
                }

                if (propInit &&
                propInit !== Array && propInit !== ObservableArray && propInit !== LazyObservableArray &&
                propInit !== DataSource && propInit !== HierarchicalDataSource) {

                    if (propValue instanceof Date) {
                        destination[property] = new Date(propValue.getTime());
                    } else if (isFunction(propValue.clone)) {
                        destination[property] = propValue.clone();
                    } else {
                        destProp = destination[property];
                        if (typeof (destProp) === OBJECT) {
                            destination[property] = destProp || {};
                        } else {
                            destination[property] = {};
                        }
                        deepExtendOne(destination[property], propValue);
                    }
                } else if (propType !== UNDEFINED) {
                    destination[property] = propValue;
                }
            }

            return destination;
        }

        function testRx(agent, rxs, dflt) {
            for (var rx in rxs) {
                if (rxs.hasOwnProperty(rx) && rxs[rx].test(agent)) {
                    return rx;
                }
            }
            return dflt !== undefined ? dflt : agent;
        }

        function toHyphens(str) {
            return str.replace(/([a-z][A-Z])/g, function (g) {
                return g.charAt(0) + '-' + g.charAt(1).toLowerCase();
            });
        }

        function toCamelCase(str) {
            return str.replace(/\-(\w)/g, function (strMatch, g1) {
                return g1.toUpperCase();
            });
        }

        function getComputedStyles(element, properties) {
            var styles = {}, computedStyle;

            if (document.defaultView && document.defaultView.getComputedStyle) {
                computedStyle = document.defaultView.getComputedStyle(element, "");

                if (properties) {
                    $.each(properties, function (idx, value) {
                        styles[value] = computedStyle.getPropertyValue(value);
                    });
                }
            } else {
                computedStyle = element.currentStyle;

                if (properties) {
                    $.each(properties, function (idx, value) {
                        styles[value] = computedStyle[toCamelCase(value)];
                    });
                }
            }

            if (!kendo.size(styles)) {
                styles = computedStyle;
            }

            return styles;
        }

        (function () {
            support._scrollbar = undefined;

            support.scrollbar = function (refresh) {
                if (!isNaN(support._scrollbar) && !refresh) {
                    return support._scrollbar;
                } else {
                    var div = document.createElement("div"),
                    result;

                    div.style.cssText = "overflow:scroll;overflow-x:hidden;zoom:1;clear:both;display:block";
                    div.innerHTML = "&nbsp;";
                    document.body.appendChild(div);

                    support._scrollbar = result = div.offsetWidth - div.scrollWidth;

                    document.body.removeChild(div);

                    return result;
                }
            };

            support.isRtl = function (element) {
                return $(element).closest(".k-rtl").length > 0;
            };

            var table = document.createElement("table");

            // Internet Explorer does not support setting the innerHTML of TBODY and TABLE elements
            try {
                table.innerHTML = "<tr><td></td></tr>";

                support.tbodyInnerHtml = true;
            } catch (e) {
                support.tbodyInnerHtml = false;
            }

            support.touch = "ontouchstart" in window;
            support.msPointers = window.MSPointerEvent;
            support.pointers = window.PointerEvent;

            var transitions = support.transitions = false,
            transforms = support.transforms = false,
            elementProto = "HTMLElement" in window ? HTMLElement.prototype : [];

            support.hasHW3D = ("WebKitCSSMatrix" in window && "m11" in new window.WebKitCSSMatrix()) || "MozPerspective" in document.documentElement.style || "msPerspective" in document.documentElement.style;

            each(["Moz", "webkit", "O", "ms"], function () {
                var prefix = this.toString(),
                hasTransitions = typeof table.style[prefix + "Transition"] === STRING;

                if (hasTransitions || typeof table.style[prefix + "Transform"] === STRING) {
                    var lowPrefix = prefix.toLowerCase();

                    transforms = {
                        css: (lowPrefix != "ms") ? "-" + lowPrefix + "-" : "",
                        prefix: prefix,
                        event: (lowPrefix === "o" || lowPrefix === "webkit") ? lowPrefix : ""
                    };

                    if (hasTransitions) {
                        transitions = transforms;
                        transitions.event = transitions.event ? transitions.event + "TransitionEnd" : "transitionend";
                    }

                    return false;
                }
            });

            table = null;

            support.transforms = transforms;
            support.transitions = transitions;

            support.devicePixelRatio = window.devicePixelRatio === undefined ? 1 : window.devicePixelRatio;

            try {
                support.screenWidth = window.outerWidth || window.screen ? window.screen.availWidth : window.innerWidth;
                support.screenHeight = window.outerHeight || window.screen ? window.screen.availHeight : window.innerHeight;
            } catch (e) {
                //window.outerWidth throws error when in IE showModalDialog.
                support.screenWidth = window.screen.availWidth;
                support.screenHeight = window.screen.availHeight;
            }

            support.detectOS = function (ua) {
                var os = false, minorVersion, match = [],
                notAndroidPhone = !/mobile safari/i.test(ua),
                agentRxs = {
                    wp: /(Windows Phone(?: OS)?)\s(\d+)\.(\d+(\.\d+)?)/,
                    fire: /(Silk)\/(\d+)\.(\d+(\.\d+)?)/,
                    android: /(Android|Android.*(?:Opera|Firefox).*?\/)\s*(\d+)\.(\d+(\.\d+)?)/,
                    iphone: /(iPhone|iPod).*OS\s+(\d+)[\._]([\d\._]+)/,
                    ipad: /(iPad).*OS\s+(\d+)[\._]([\d_]+)/,
                    meego: /(MeeGo).+NokiaBrowser\/(\d+)\.([\d\._]+)/,
                    webos: /(webOS)\/(\d+)\.(\d+(\.\d+)?)/,
                    blackberry: /(BlackBerry|BB10).*?Version\/(\d+)\.(\d+(\.\d+)?)/,
                    playbook: /(PlayBook).*?Tablet\s*OS\s*(\d+)\.(\d+(\.\d+)?)/,
                    windows: /(MSIE)\s+(\d+)\.(\d+(\.\d+)?)/,
                    tizen: /(tizen).*?Version\/(\d+)\.(\d+(\.\d+)?)/i,
                    sailfish: /(sailfish).*rv:(\d+)\.(\d+(\.\d+)?).*firefox/i,
                    ffos: /(Mobile).*rv:(\d+)\.(\d+(\.\d+)?).*Firefox/
                },
                osRxs = {
                    ios: /^i(phone|pad|pod)$/i,
                    android: /^android|fire$/i,
                    blackberry: /^blackberry|playbook/i,
                    windows: /windows/,
                    wp: /wp/,
                    flat: /sailfish|ffos|tizen/i,
                    meego: /meego/
                },
                formFactorRxs = {
                    tablet: /playbook|ipad|fire/i
                },
                browserRxs = {
                    omini: /Opera\sMini/i,
                    omobile: /Opera\sMobi/i,
                    firefox: /Firefox|Fennec/i,
                    mobilesafari: /version\/.*safari/i,
                    ie: /MSIE|Windows\sPhone/i,
                    chrome: /chrome|crios/i,
                    webkit: /webkit/i
                };

                for (var agent in agentRxs) {
                    if (agentRxs.hasOwnProperty(agent)) {
                        match = ua.match(agentRxs[agent]);
                        if (match) {
                            if (agent == "windows" && "plugins" in navigator) { return false; } // Break if not Metro/Mobile Windows

                            os = {};
                            os.device = agent;
                            os.tablet = testRx(agent, formFactorRxs, false);
                            os.browser = testRx(ua, browserRxs, "default");
                            os.name = testRx(agent, osRxs);
                            os[os.name] = true;
                            os.majorVersion = match[2];
                            os.minorVersion = match[3].replace("_", ".");
                            minorVersion = os.minorVersion.replace(".", "").substr(0, 2);
                            os.flatVersion = os.majorVersion + minorVersion + (new Array(3 - (minorVersion.length < 3 ? minorVersion.length : 2)).join("0"));
                            os.cordova = typeof window.PhoneGap !== UNDEFINED || typeof window.cordova !== UNDEFINED; // Use file protocol to detect appModes.
                            os.appMode = window.navigator.standalone || (/file|local|wmapp/).test(window.location.protocol) || os.cordova; // Use file protocol to detect appModes.

                            if (os.android && (support.devicePixelRatio < 1.5 && os.flatVersion < 400 || notAndroidPhone) && (support.screenWidth > 800 || support.screenHeight > 800)) {
                                os.tablet = agent;
                            }

                            break;
                        }
                    }
                }
                return os;
            };

            var mobileOS = support.mobileOS = support.detectOS(navigator.userAgent);

            support.wpDevicePixelRatio = mobileOS.wp ? screen.width / 320 : 0;
            support.kineticScrollNeeded = mobileOS && (support.touch || support.msPointers || support.pointers);

            support.hasNativeScrolling = false;

            if (mobileOS.ios || (mobileOS.android && mobileOS.majorVersion > 2) || mobileOS.wp) {
                support.hasNativeScrolling = mobileOS;
            }

            support.mouseAndTouchPresent = support.touch && !(support.mobileOS.ios || support.mobileOS.android);

            support.detectBrowser = function (ua) {
                var browser = false, match = [],
                browserRxs = {
                    webkit: /(chrome)[ \/]([\w.]+)/i,
                    safari: /(webkit)[ \/]([\w.]+)/i,
                    opera: /(opera)(?:.*version|)[ \/]([\w.]+)/i,
                    msie: /(msie\s|trident.*? rv:)([\w.]+)/i,
                    mozilla: /(mozilla)(?:.*? rv:([\w.]+)|)/i
                };

                for (var agent in browserRxs) {
                    if (browserRxs.hasOwnProperty(agent)) {
                        match = ua.match(browserRxs[agent]);
                        if (match) {
                            browser = {};
                            browser[agent] = true;
                            browser[match[1].toLowerCase().split(" ")[0].split("/")[0]] = true;
                            browser.version = parseInt(document.documentMode || match[2], 10);

                            break;
                        }
                    }
                }

                return browser;
            };

            support.browser = support.detectBrowser(navigator.userAgent);

            support.zoomLevel = function () {
                try {
                    return support.touch ? (document.documentElement.clientWidth / window.innerWidth) :
                       support.browser.msie && support.browser.version >= 10 ? ((top || window).document.documentElement.offsetWidth / (top || window).innerWidth) : 1;
                } catch (e) {
                    return 1;
                }
            };

            support.cssBorderSpacing = typeof document.documentElement.style.borderSpacing != "undefined" && !(support.browser.msie && support.browser.version < 8);

            (function (browser) {
                // add browser-specific CSS class
                var cssClass = "",
                docElement = $(document.documentElement),
                majorVersion = parseInt(browser.version, 10);

                if (browser.msie) {
                    cssClass = "ie";
                } else if (browser.mozilla) {
                    cssClass = "ff";
                } else if (browser.safari) {
                    cssClass = "safari";
                } else if (browser.webkit) {
                    cssClass = "webkit";
                } else if (browser.opera) {
                    cssClass = "opera";
                }

                if (cssClass) {
                    cssClass = "k-" + cssClass + " k-" + cssClass + majorVersion;
                }
                if (support.mobileOS) {
                    cssClass += " k-mobile";
                }

                docElement.addClass(cssClass);
            })(support.browser);

            support.eventCapture = document.documentElement.addEventListener;

            var input = document.createElement("input");

            support.placeholder = "placeholder" in input;
            support.propertyChangeEvent = "onpropertychange" in input;

            support.input = (function () {
                var types = ["number", "date", "time", "month", "week", "datetime", "datetime-local"];
                var length = types.length;
                var value = "test";
                var result = {};
                var idx = 0;
                var type;

                for (; idx < length; idx++) {
                    type = types[idx];
                    input.setAttribute("type", type);
                    input.value = value;

                    result[type.replace("-", "")] = input.type !== "text" && input.value !== value;
                }

                return result;
            })();

            input.style.cssText = "float:left;";

            support.cssFloat = !!input.style.cssFloat;

            input = null;

            support.stableSort = (function () {
                // Chrome sort is not stable for more than *10* items
                // IE9+ sort is not stable for than *512* items
                var threshold = 513;

                var sorted = [{
                    index: 0,
                    field: "b"
                }];

                for (var i = 1; i < threshold; i++) {
                    sorted.push({
                        index: i,
                        field: "a"
                    });
                }

                sorted.sort(function (a, b) {
                    return a.field > b.field ? 1 : (a.field < b.field ? -1 : 0);
                });

                return sorted[0].index === 1;
            })();

            support.matchesSelector = elementProto.webkitMatchesSelector || elementProto.mozMatchesSelector ||
                                  elementProto.msMatchesSelector || elementProto.oMatchesSelector ||
                                  elementProto.matchesSelector || elementProto.matches ||
          function (selector) {
              var nodeList = document.querySelectorAll ? (this.parentNode || document).querySelectorAll(selector) || [] : $(selector),
                  i = nodeList.length;

              while (i--) {
                  if (nodeList[i] == this) {
                      return true;
                  }
              }

              return false;
          };

            support.pushState = window.history && window.history.pushState;

            var documentMode = document.documentMode;

            support.hashChange = ("onhashchange" in window) && !(support.browser.msie && (!documentMode || documentMode <= 8)); // old IE detection
        })();


        function size(obj) {
            var result = 0, key;
            for (key in obj) {
                if (obj.hasOwnProperty(key) && key != "toJSON") { // Ignore fake IE7 toJSON.
                    result++;
                }
            }

            return result;
        }

        function getOffset(element, type, positioned) {
            if (!type) {
                type = "offset";
            }

            var result = element[type](),
            mobileOS = support.mobileOS;

            // IE10 touch zoom is living in a separate viewport
            if (support.browser.msie && (support.pointers || support.msPointers) && !positioned) {
                result.top -= (window.pageYOffset - document.documentElement.scrollTop);
                result.left -= (window.pageXOffset - document.documentElement.scrollLeft);
            }

            return result;
        }

        var directions = {
            left: { reverse: "right" },
            right: { reverse: "left" },
            down: { reverse: "up" },
            up: { reverse: "down" },
            top: { reverse: "bottom" },
            bottom: { reverse: "top" },
            "in": { reverse: "out" },
            out: { reverse: "in" }
        };

        function parseEffects(input) {
            var effects = {};

            each((typeof input === "string" ? input.split(" ") : input), function (idx) {
                effects[idx] = this;
            });

            return effects;
        }

        function fx(element) {
            return new kendo.effects.Element(element);
        }

        var effects = {};

        $.extend(effects, {
            enabled: true,
            Element: function (element) {
                this.element = $(element);
            },

            promise: function (element, options) {
                if (!element.is(":visible")) {
                    element.css({ display: element.data("olddisplay") || "block" }).css("display");
                }

                if (options.hide) {
                    element.data("olddisplay", element.css("display")).hide();
                }

                if (options.init) {
                    options.init();
                }

                if (options.completeCallback) {
                    options.completeCallback(element); // call the external complete callback with the element
                }

                element.dequeue();
            },

            disable: function () {
                this.enabled = false;
                this.promise = this.promiseShim;
            },

            enable: function () {
                this.enabled = true;
                this.promise = this.animatedPromise;
            }
        });

        effects.promiseShim = effects.promise;

        function prepareAnimationOptions(options, duration, reverse, complete) {
            if (typeof options === STRING) {
                // options is the list of effect names separated by space e.g. animate(element, "fadeIn slideDown")

                // only callback is provided e.g. animate(element, options, function() {});
                if (isFunction(duration)) {
                    complete = duration;
                    duration = 400;
                    reverse = false;
                }

                if (isFunction(reverse)) {
                    complete = reverse;
                    reverse = false;
                }

                if (typeof duration === BOOLEAN) {
                    reverse = duration;
                    duration = 400;
                }

                options = {
                    effects: options,
                    duration: duration,
                    reverse: reverse,
                    complete: complete
                };
            }

            return extend({
                //default options
                effects: {},
                duration: 400, //jQuery default duration
                reverse: false,
                init: noop,
                teardown: noop,
                hide: false
            }, options, { completeCallback: options.complete, complete: noop }); // Move external complete callback, so deferred.resolve can be always executed.

        }

        function animate(element, options, duration, reverse, complete) {
            var idx = 0,
            length = element.length,
            instance;

            for (; idx < length; idx++) {
                instance = $(element[idx]);
                instance.queue(function () {
                    effects.promise(instance, prepareAnimationOptions(options, duration, reverse, complete));
                });
            }

            return element;
        }

        function toggleClass(element, classes, options, add) {
            if (classes) {
                classes = classes.split(" ");

                each(classes, function (idx, value) {
                    element.toggleClass(value, add);
                });
            }

            return element;
        }

        if (!("kendoAnimate" in $.fn)) {
            extend($.fn, {
                kendoStop: function (clearQueue, gotoEnd) {
                    return this.stop(clearQueue, gotoEnd);
                },

                kendoAnimate: function (options, duration, reverse, complete) {
                    return animate(this, options, duration, reverse, complete);
                },

                kendoAddClass: function (classes, options) {
                    return kendo.toggleClass(this, classes, options, true);
                },

                kendoRemoveClass: function (classes, options) {
                    return kendo.toggleClass(this, classes, options, false);
                },
                kendoToggleClass: function (classes, options, toggle) {
                    return kendo.toggleClass(this, classes, options, toggle);
                }
            });
        }

        var ampRegExp = /&/g,
        ltRegExp = /</g,
        quoteRegExp = /"/g,
        aposRegExp = /'/g,
        gtRegExp = />/g;
        function htmlEncode(value) {
            return ("" + value).replace(ampRegExp, "&amp;").replace(ltRegExp, "&lt;").replace(gtRegExp, "&gt;").replace(quoteRegExp, "&quot;").replace(aposRegExp, "&#39;");
        }

        var eventTarget = function (e) {
            return e.target;
        };

        if (support.touch) {

            eventTarget = function (e) {
                var touches = "originalEvent" in e ? e.originalEvent.changedTouches : "changedTouches" in e ? e.changedTouches : null;

                return touches ? document.elementFromPoint(touches[0].clientX, touches[0].clientY) : e.target;
            };

            each(["swipe", "swipeLeft", "swipeRight", "swipeUp", "swipeDown", "doubleTap", "tap"], function (m, value) {
                $.fn[value] = function (callback) {
                    return this.bind(value, callback);
                };
            });
        }

        if (support.touch) {
            if (!support.mobileOS) {
                support.mousedown = "mousedown touchstart";
                support.mouseup = "mouseup touchend";
                support.mousemove = "mousemove touchmove";
                support.mousecancel = "mouseleave touchcancel";
                support.click = "click";
                support.resize = "resize";
            } else {
                support.mousedown = "touchstart";
                support.mouseup = "touchend";
                support.mousemove = "touchmove";
                support.mousecancel = "touchcancel";
                support.click = "touchend";
                support.resize = "orientationchange";
            }
        } else if (support.pointers) {
            support.mousemove = "pointermove";
            support.mousedown = "pointerdown";
            support.mouseup = "pointerup";
            support.mousecancel = "pointercancel";
            support.click = "pointerup";
            support.resize = "orientationchange resize";
        } else if (support.msPointers) {
            support.mousemove = "MSPointerMove";
            support.mousedown = "MSPointerDown";
            support.mouseup = "MSPointerUp";
            support.mousecancel = "MSPointerCancel";
            support.click = "MSPointerUp";
            support.resize = "orientationchange resize";
        } else {
            support.mousemove = "mousemove";
            support.mousedown = "mousedown";
            support.mouseup = "mouseup";
            support.mousecancel = "mouseleave";
            support.click = "click";
            support.resize = "resize";
        }

        var wrapExpression = function (members, paramName) {
            var result = paramName || "d",
            index,
            idx,
            length,
            member,
            count = 1;

            for (idx = 0, length = members.length; idx < length; idx++) {
                member = members[idx];
                if (member !== "") {
                    index = member.indexOf("[");

                    if (index !== 0) {
                        if (index == -1) {
                            member = "." + member;
                        } else {
                            count++;
                            member = "." + member.substring(0, index) + " || {})" + member.substring(index);
                        }
                    }

                    count++;
                    result += member + ((idx < length - 1) ? " || {})" : ")");
                }
            }
            return new Array(count).join("(") + result;
        },
    localUrlRe = /^([a-z]+:)?\/\//i;

        extend(kendo, {
            ui: kendo.ui || {},
            fx: kendo.fx || fx,
            effects: kendo.effects || effects,
            mobile: kendo.mobile || {},
            data: kendo.data || {},
            dataviz: kendo.dataviz || {},
            keys: {
                INSERT: 45,
                DELETE: 46,
                BACKSPACE: 8,
                TAB: 9,
                ENTER: 13,
                ESC: 27,
                LEFT: 37,
                UP: 38,
                RIGHT: 39,
                DOWN: 40,
                END: 35,
                HOME: 36,
                SPACEBAR: 32,
                PAGEUP: 33,
                PAGEDOWN: 34,
                F2: 113,
                F10: 121,
                F12: 123,
                NUMPAD_PLUS: 107,
                NUMPAD_MINUS: 109,
                NUMPAD_DOT: 110
            },
            support: kendo.support || support,
            animate: kendo.animate || animate,
            ns: "",
            attr: function (value) {
                return "data-" + kendo.ns + value;
            },
            getShadows: getShadows,
            wrap: wrap,
            deepExtend: deepExtend,
            getComputedStyles: getComputedStyles,
            size: size,
            toCamelCase: toCamelCase,
            toHyphens: toHyphens,
            getOffset: kendo.getOffset || getOffset,
            parseEffects: kendo.parseEffects || parseEffects,
            toggleClass: kendo.toggleClass || toggleClass,
            directions: kendo.directions || directions,
            Observable: Observable,
            Class: Class,
            Template: Template,
            template: proxy(Template.compile, Template),
            render: proxy(Template.render, Template),
            stringify: proxy(JSON.stringify, JSON),
            eventTarget: eventTarget,
            htmlEncode: htmlEncode,
            isLocalUrl: function (url) {
                return url && !localUrlRe.test(url);
            },

            expr: function (expression, safe, paramName) {
                expression = expression || "";

                if (typeof safe == STRING) {
                    paramName = safe;
                    safe = false;
                }

                paramName = paramName || "d";

                if (expression && expression.charAt(0) !== "[") {
                    expression = "." + expression;
                }

                if (safe) {
                    expression = wrapExpression(expression.split("."), paramName);
                } else {
                    expression = paramName + expression;
                }

                return expression;
            },

            getter: function (expression, safe) {
                var key = expression + safe;
                return getterCache[key] = getterCache[key] || new Function("d", "return " + kendo.expr(expression, safe));
            },

            setter: function (expression) {
                return setterCache[expression] = setterCache[expression] || new Function("d,value", kendo.expr(expression) + "=value");
            },

            accessor: function (expression) {
                return {
                    get: kendo.getter(expression),
                    set: kendo.setter(expression)
                };
            },

            guid: function () {
                var id = "", i, random;

                for (i = 0; i < 32; i++) {
                    random = math.random() * 16 | 0;

                    if (i == 8 || i == 12 || i == 16 || i == 20) {
                        id += "-";
                    }
                    id += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
                }

                return id;
            },

            roleSelector: function (role) {
                return role.replace(/(\S+)/g, "[" + kendo.attr("role") + "=$1],").slice(0, -1);
            },

            directiveSelector: function (directives) {
                var selectors = directives.split(" ");

                if (selectors) {
                    for (var i = 0; i < selectors.length; i++) {
                        if (selectors[i] != "view") {
                            selectors[i] = selectors[i].replace(/(\w*)(view|bar|strip|over)$/, "$1-$2");
                        }
                    }
                }

                return selectors.join(" ").replace(/(\S+)/g, "kendo-mobile-$1,").slice(0, -1);
            },

            triggeredByInput: function (e) {
                return (/^(label|input|textarea|select)$/i).test(e.target.tagName);
            },

            logToConsole: function (message) {
                var console = window.console;

                if (!kendo.suppressLog && typeof (console) != "undefined" && console.log) {
                    console.log(message);
                }
            }
        });

        var Widget = Observable.extend({
            init: function (element, options) {
                var that = this;

                that.element = kendo.jQuery(element).handler(that);

                that.angular("init", options);

                Observable.fn.init.call(that);

                var dataSource = options ? options.dataSource : null;

                if (dataSource) {
                    // avoid deep cloning the data source
                    options = extend({}, options, { dataSource: {} });
                }

                options = that.options = extend(true, {}, that.options, options);

                if (dataSource) {
                    options.dataSource = dataSource;
                }

                if (!that.element.attr(kendo.attr("role"))) {
                    that.element.attr(kendo.attr("role"), (options.name || "").toLowerCase());
                }

                that.element.data("kendo" + options.prefix + options.name, that);

                that.bind(that.events, options);
            },

            events: [],

            options: {
                prefix: ""
            },

            _hasBindingTarget: function () {
                return !!this.element[0].kendoBindingTarget;
            },

            _tabindex: function (target) {
                target = target || this.wrapper;

                var element = this.element,
                TABINDEX = "tabindex",
                tabindex = target.attr(TABINDEX) || element.attr(TABINDEX);

                element.removeAttr(TABINDEX);

                target.attr(TABINDEX, !isNaN(tabindex) ? tabindex : 0);
            },

            setOptions: function (options) {
                this._setEvents(options);
                $.extend(this.options, options);
            },

            _setEvents: function (options) {
                var that = this,
                idx = 0,
                length = that.events.length,
                e;

                for (; idx < length; idx++) {
                    e = that.events[idx];
                    if (that.options[e] && options[e]) {
                        that.unbind(e, that.options[e]);
                    }
                }

                that.bind(that.events, options);
            },

            resize: function (force) {
                var size = this.getSize(),
                currentSize = this._size;

                if (force || !currentSize || size.width !== currentSize.width || size.height !== currentSize.height) {
                    this._size = size;
                    this._resize(size);
                    this.trigger("resize", size);
                }
            },

            getSize: function () {
                return kendo.dimensions(this.element);
            },

            size: function (size) {
                if (!size) {
                    return this.getSize();
                } else {
                    this.setSize(size);
                }
            },

            setSize: $.noop,
            _resize: $.noop,

            destroy: function () {
                var that = this;

                that.element.removeData("kendo" + that.options.prefix + that.options.name);
                that.element.removeData("handler");
                that.unbind();
            },

            angular: function () { }
        });

        var DataBoundWidget = Widget.extend({
            // Angular consumes these.
            dataItems: function () {
                return this.dataSource.flatView();
            },

            _angularItems: function (cmd) {
                var that = this;
                that.angular(cmd, function () {
                    return {
                        elements: that.items(),
                        data: $.map(that.dataItems(), function (dataItem) {
                            return { dataItem: dataItem };
                        })
                    };
                });
            }
        });

        kendo.dimensions = function (element, dimensions) {
            var domElement = element[0];

            if (dimensions) {
                element.css(dimensions);
            }

            return { width: domElement.offsetWidth, height: domElement.offsetHeight };
        };

        kendo.notify = noop;

        var templateRegExp = /template$/i,
        jsonRegExp = /^\s*(?:\{(?:.|\r\n|\n)*\}|\[(?:.|\r\n|\n)*\])\s*$/,
        jsonFormatRegExp = /^\{(\d+)(:[^\}]+)?\}|^\[[A-Za-z_]*\]$/,
        dashRegExp = /([A-Z])/g;

        function parseOption(element, option) {
            var value;

            if (option.indexOf("data") === 0) {
                option = option.substring(4);
                option = option.charAt(0).toLowerCase() + option.substring(1);
            }

            option = option.replace(dashRegExp, "-$1");
            value = element.getAttribute("data-" + kendo.ns + option);

            if (value === null) {
                value = undefined;
            } else if (value === "null") {
                value = null;
            } else if (value === "true") {
                value = true;
            } else if (value === "false") {
                value = false;
            } else if (numberRegExp.test(value)) {
                value = parseFloat(value);
            } else if (jsonRegExp.test(value) && !jsonFormatRegExp.test(value)) {
                value = new Function("return (" + value + ")")();
            }

            return value;
        }

        function parseOptions(element, options) {
            var result = {},
            option,
            value;

            for (option in options) {
                value = parseOption(element, option);

                if (value !== undefined) {

                    if (templateRegExp.test(option)) {
                        value = kendo.template($("#" + value).html());
                    }

                    result[option] = value;
                }
            }

            return result;
        }

        kendo.initWidget = function (element, options, roles) {
            var result,
            option,
            widget,
            idx,
            length,
            role,
            value,
            dataSource,
            fullPath,
            widgetKeyRegExp;

            // Preserve backwards compatibility with (element, options, namespace) signature, where namespace was kendo.ui
            if (!roles) {
                roles = kendo.ui.roles;
            } else if (roles.roles) {
                roles = roles.roles;
            }

            element = element.nodeType ? element : element[0];

            role = element.getAttribute("data-" + kendo.ns + "role");

            if (!role) {
                return;
            }

            fullPath = role.indexOf(".") === -1;

            // look for any widget that may be already instantiated based on this role.
            // The prefix used is unknown, hence the regexp
            //

            if (fullPath) {
                widget = roles[role];
            } else { // full namespace path - like kendo.ui.Widget
                widget = kendo.getter(role)(window);
            }

            var data = $(element).data(),
            widgetKey = widget ? "kendo" + widget.fn.options.prefix + widget.fn.options.name : "";

            if (fullPath) {
                widgetKeyRegExp = new RegExp("^kendo.*" + role + "$", "i");
            } else { // full namespace path - like kendo.ui.Widget
                widgetKeyRegExp = new RegExp("^" + widgetKey + "$", "i");
            }

            for (var key in data) {
                if (key.match(widgetKeyRegExp)) {
                    // we have detected a widget of the same kind - save its reference, we will set its options
                    if (key === widgetKey) {
                        result = data[key];
                    } else {
                        return data[key];
                    }
                }
            }

            if (!widget) {
                return;
            }

            dataSource = parseOption(element, "dataSource");

            options = $.extend({}, parseOptions(element, widget.fn.options), options);

            if (dataSource) {
                if (typeof dataSource === STRING) {
                    options.dataSource = kendo.getter(dataSource)(window);
                } else {
                    options.dataSource = dataSource;
                }
            }

            for (idx = 0, length = widget.fn.events.length; idx < length; idx++) {
                option = widget.fn.events[idx];

                value = parseOption(element, option);

                if (value !== undefined) {
                    options[option] = kendo.getter(value)(window);
                }
            }

            if (!result) {
                result = new widget(element, options);
            } else if (!$.isEmptyObject(options)) {
                result.setOptions(options);
            }

            return result;
        };

        kendo.rolesFromNamespaces = function (namespaces) {
            var roles = [],
            idx,
            length;

            if (!namespaces[0]) {
                namespaces = [kendo.ui, kendo.dataviz.ui];
            }

            for (idx = 0, length = namespaces.length; idx < length; idx++) {
                roles[idx] = namespaces[idx].roles;
            }

            return extend.apply(null, [{}].concat(roles.reverse()));
        };

        kendo.init = function (element) {
            var roles = kendo.rolesFromNamespaces(slice.call(arguments, 1));

            $(element).find("[data-" + kendo.ns + "role]").addBack().each(function () {
                kendo.initWidget(this, {}, roles);
            });
        };

        kendo.destroy = function (element) {
            $(element).find("[data-" + kendo.ns + "role]").addBack().each(function () {
                var data = $(this).data();

                for (var key in data) {
                    if (key.indexOf("kendo") === 0 && typeof data[key].destroy === FUNCTION) {
                        data[key].destroy();
                    }
                }
            });
        };

        function containmentComparer(a, b) {
            return $.contains(a, b) ? -1 : 1;
        }

        function resizableWidget() {
            var widget = $(this);
            return ($.inArray(widget.attr("data-" + kendo.ns + "role"), ["slider", "rangeslider"]) > -1) || widget.is(":visible");
        }

        kendo.resize = function (element, force) {
            var widgets = $(element).find("[data-" + kendo.ns + "role]").addBack().filter(resizableWidget);

            if (!widgets.length) {
                return;
            }

            // sort widgets based on their parent-child relation
            var widgetsArray = $.makeArray(widgets);
            widgetsArray.sort(containmentComparer);

            // resize widgets
            $.each(widgetsArray, function () {
                var widget = kendo.widgetInstance($(this));
                if (widget) {
                    widget.resize(force);
                }
            });
        };

        kendo.parseOptions = parseOptions;

        extend(kendo.ui, {
            Widget: Widget,
            DataBoundWidget: DataBoundWidget,
            roles: {},
            progress: function (container, toggle) {
                var mask = container.find(".k-loading-mask"),
                support = kendo.support,
                browser = support.browser,
                isRtl, leftRight, webkitCorrection, containerScrollLeft;

                if (toggle) {
                    if (!mask.length) {
                        isRtl = support.isRtl(container);
                        leftRight = isRtl ? "right" : "left";
                        containerScrollLeft = container.scrollLeft();
                        webkitCorrection = browser.webkit ? (!isRtl ? 0 : container[0].scrollWidth - container.width() - 2 * containerScrollLeft) : 0;

                        mask = $("<div class='k-loading-mask'><span class='k-loading-text'>Loading...</span><div class='k-loading-image'/><div class='k-loading-color'/></div>")
                        .width("100%").height("100%")
                        .css("top", container.scrollTop())
                        .css(leftRight, Math.abs(containerScrollLeft) + webkitCorrection)
                        .prependTo(container);
                    }
                } else if (mask) {
                    mask.remove();
                }
            },
            plugin: function (widget, register, prefix) {
                var name = widget.fn.options.name,
                getter;

                register = register || kendo.ui;
                prefix = prefix || "";

                register[name] = widget;

                register.roles[name.toLowerCase()] = widget;

                getter = "getKendo" + prefix + name;
                name = "kendo" + prefix + name;

                $.fn[name] = function (options) {
                    var value = this,
                    args;

                    if (typeof options === STRING) {
                        args = slice.call(arguments, 1);

                        this.each(function () {
                            var widget = $.data(this, name),
                            method,
                            result;

                            if (!widget) {
                                throw new Error(kendo.format("Cannot call method '{0}' of {1} before it is initialized", options, name));
                            }

                            method = widget[options];

                            if (typeof method !== FUNCTION) {
                                throw new Error(kendo.format("Cannot find method '{0}' of {1}", options, name));
                            }

                            result = method.apply(widget, args);

                            if (result !== undefined) {
                                value = result;
                                return false;
                            }
                        });
                    } else {
                        this.each(function () {
                            new widget(this, options);
                        });
                    }

                    return value;
                };

                $.fn[name].widget = widget;

                $.fn[getter] = function () {
                    return this.data(name);
                };
            }
        });

        var ContainerNullObject = { bind: function () { return this; }, nullObject: true, options: {} };

        var MobileWidget = Widget.extend({
            init: function (element, options) {
                Widget.fn.init.call(this, element, options);
                this.element.autoApplyNS();
                this.wrapper = this.element;
                this.element.addClass("km-widget");
            },

            destroy: function () {
                Widget.fn.destroy.call(this);
                this.element.kendoDestroy();
            },

            options: {
                prefix: "Mobile"
            },

            events: [],

            view: function () {
                var viewElement = this.element.closest(kendo.roleSelector("view splitview modalview drawer"));
                return kendo.widgetInstance(viewElement, kendo.mobile.ui) || ContainerNullObject;
            },

            viewHasNativeScrolling: function () {
                var view = this.view();
                return view && view.options.useNativeScrolling;
            },

            container: function () {
                var element = this.element.closest(kendo.roleSelector("view layout modalview drawer splitview"));
                return kendo.widgetInstance(element.eq(0), kendo.mobile.ui) || ContainerNullObject;
            }
        });

        extend(kendo.mobile, {
            init: function (element) {
                kendo.init(element, kendo.mobile.ui, kendo.ui, kendo.dataviz.ui);
            },

            appLevelNativeScrolling: function () {
                return kendo.mobile.application && kendo.mobile.application.options && kendo.mobile.application.options.useNativeScrolling;
            },

            roles: {},

            ui: {
                Widget: MobileWidget,
                DataBoundWidget: DataBoundWidget.extend(MobileWidget.prototype),
                roles: {},
                plugin: function (widget) {
                    kendo.ui.plugin(widget, kendo.mobile.ui, "Mobile");
                }
            }
        });

        deepExtend(kendo.dataviz, {
            init: function (element) {
                kendo.init(element, kendo.dataviz.ui);
            },
            ui: {
                roles: {},
                themes: {},
                views: [],
                plugin: function (widget) {
                    kendo.ui.plugin(widget, kendo.dataviz.ui);
                }
            },
            roles: {}
        });

        kendo.touchScroller = function (elements, options) {
            // return the first touch scroller
            return $(elements).map(function (idx, element) {
                element = $(element);
                if (support.kineticScrollNeeded && kendo.mobile.ui.Scroller && !element.data("kendoMobileScroller")) {
                    element.kendoMobileScroller(options);
                    return element.data("kendoMobileScroller");
                } else {
                    return false;
                }
            })[0];
        };

        kendo.preventDefault = function (e) {
            e.preventDefault();
        };

        kendo.widgetInstance = function (element, suites) {
            var role = element.data(kendo.ns + "role"),
            widgets = [], i, length;

            if (role) {
                // HACK!!! mobile view scroller widgets are instantiated on data-role="content" elements. We need to discover them when resizing.
                if (role === "content") {
                    role = "scroller";
                }

                if (suites) {
                    if (suites[0]) {
                        for (i = 0, length = suites.length; i < length; i++) {
                            widgets.push(suites[i].roles[role]);
                        }
                    } else {
                        widgets.push(suites.roles[role]);
                    }
                }
                else {
                    widgets = [kendo.ui.roles[role], kendo.dataviz.ui.roles[role], kendo.mobile.ui.roles[role]];
                }

                if (role.indexOf(".") >= 0) {
                    widgets = [kendo.getter(role)(window)];
                }

                for (i = 0, length = widgets.length; i < length; i++) {
                    var widget = widgets[i];
                    if (widget) {
                        var instance = element.data("kendo" + widget.fn.options.prefix + widget.fn.options.name);
                        if (instance) {
                            return instance;
                        }
                    }
                }
            }
        };

        kendo.onResize = function (callback) {
            var handler = callback;
            if (support.mobileOS.android) {
                handler = function () { setTimeout(callback, 600); };
            }

            $(window).on(support.resize, handler);
            return handler;
        };

        kendo.unbindResize = function (callback) {
            $(window).off(support.resize, callback);
        };

        kendo.attrValue = function (element, key) {
            return element.data(kendo.ns + key);
        };

        kendo.days = {
            Sunday: 0,
            Monday: 1,
            Tuesday: 2,
            Wednesday: 3,
            Thursday: 4,
            Friday: 5,
            Saturday: 6
        };

        function focusable(element, isTabIndexNotNaN) {
            var nodeName = element.nodeName.toLowerCase();

            return (/input|select|textarea|button|object/.test(nodeName) ?
                !element.disabled :
                "a" === nodeName ?
                element.href || isTabIndexNotNaN :
                isTabIndexNotNaN
               ) &&
            visible(element);
        }

        function visible(element) {
            return !$(element).parents().addBack().filter(function () {
                return $.css(this, "visibility") === "hidden" || $.expr.filters.hidden(this);
            }).length;
        }

        $.extend($.expr[":"], {
            kendoFocusable: function (element) {
                var idx = $.attr(element, "tabindex");
                return focusable(element, !isNaN(idx) && idx > -1);
            }
        });

        var MOUSE_EVENTS = ["mousedown", "mousemove", "mouseenter", "mouseleave", "mouseover", "mouseout", "mouseup", "click"];
        var EXCLUDE_BUST_CLICK_SELECTOR = "label, input, [data-rel=external]";

        var MouseEventNormalizer = {
            setupMouseMute: function () {
                var idx = 0,
                length = MOUSE_EVENTS.length,
                element = document.documentElement;

                if (MouseEventNormalizer.mouseTrap || !support.eventCapture) {
                    return;
                }

                MouseEventNormalizer.mouseTrap = true;

                MouseEventNormalizer.bustClick = false;
                MouseEventNormalizer.captureMouse = false;

                var handler = function (e) {
                    if (MouseEventNormalizer.captureMouse) {
                        if (e.type === "click") {
                            if (MouseEventNormalizer.bustClick && !$(e.target).is(EXCLUDE_BUST_CLICK_SELECTOR)) {
                                e.preventDefault();
                                e.stopPropagation();
                            }
                        } else {
                            e.stopPropagation();
                        }
                    }
                };

                for (; idx < length; idx++) {
                    element.addEventListener(MOUSE_EVENTS[idx], handler, true);
                }
            },

            muteMouse: function (e) {
                MouseEventNormalizer.captureMouse = true;
                if (e.data.bustClick) {
                    MouseEventNormalizer.bustClick = true;
                }
                clearTimeout(MouseEventNormalizer.mouseTrapTimeoutID);
            },

            unMuteMouse: function () {
                clearTimeout(MouseEventNormalizer.mouseTrapTimeoutID);
                MouseEventNormalizer.mouseTrapTimeoutID = setTimeout(function () {
                    MouseEventNormalizer.captureMouse = false;
                    MouseEventNormalizer.bustClick = false;
                }, 400);
            }
        };

        var eventMap = {
            down: "touchstart mousedown",
            move: "mousemove touchmove",
            up: "mouseup touchend touchcancel",
            cancel: "mouseleave touchcancel"
        };

        if (support.touch && (support.mobileOS.ios || support.mobileOS.android)) {
            eventMap = {
                down: "touchstart",
                move: "touchmove",
                up: "touchend touchcancel",
                cancel: "touchcancel"
            };
        } else if (support.pointers) {
            eventMap = {
                down: "pointerdown",
                move: "pointermove",
                up: "pointerup",
                cancel: "pointercancel pointerleave"
            };
        } else if (support.msPointers) {
            eventMap = {
                down: "MSPointerDown",
                move: "MSPointerMove",
                up: "MSPointerUp",
                cancel: "MSPointerCancel MSPointerLeave"
            };
        }

        if (support.msPointers && !("onmspointerenter" in window)) { // IE10
            // Create MSPointerEnter/MSPointerLeave events using mouseover/out and event-time checks
            $.each({
                MSPointerEnter: "MSPointerOver",
                MSPointerLeave: "MSPointerOut"
            }, function (orig, fix) {
                $.event.special[orig] = {
                    delegateType: fix,
                    bindType: fix,

                    handle: function (event) {
                        var ret,
                        target = this,
                        related = event.relatedTarget,
                        handleObj = event.handleObj;

                        // For mousenter/leave call the handler if related is outside the target.
                        // NB: No relatedTarget if the mouse left/entered the browser window
                        if (!related || (related !== target && !$.contains(target, related))) {
                            event.type = handleObj.origType;
                            ret = handleObj.handler.apply(this, arguments);
                            event.type = fix;
                        }
                        return ret;
                    }
                };
            });
        }


        var getEventMap = function (e) { return (eventMap[e] || e); },
        eventRegEx = /([^ ]+)/g;

        kendo.applyEventMap = function (events, ns) {
            events = events.replace(eventRegEx, getEventMap);

            if (ns) {
                events = events.replace(eventRegEx, "$1." + ns);
            }

            return events;
        };

        var on = $.fn.on;

        function kendoJQuery(selector, context) {
            return new kendoJQuery.fn.init(selector, context);
        }

        extend(true, kendoJQuery, $);

        kendoJQuery.fn = kendoJQuery.prototype = new $();

        kendoJQuery.fn.constructor = kendoJQuery;

        kendoJQuery.fn.init = function (selector, context) {
            if (context && context instanceof $ && !(context instanceof kendoJQuery)) {
                context = kendoJQuery(context);
            }

            return $.fn.init.call(this, selector, context, rootjQuery);
        };

        kendoJQuery.fn.init.prototype = kendoJQuery.fn;

        var rootjQuery = kendoJQuery(document);

        extend(kendoJQuery.fn, {
            handler: function (handler) {
                this.data("handler", handler);
                return this;
            },

            autoApplyNS: function (ns) {
                this.data("kendoNS", ns || kendo.guid());
                return this;
            },

            on: function () {
                var that = this,
                ns = that.data("kendoNS");

                // support for event map signature
                if (arguments.length === 1) {
                    return on.call(that, arguments[0]);
                }

                var context = that,
                args = slice.call(arguments);

                if (typeof args[args.length - 1] === UNDEFINED) {
                    args.pop();
                }

                var callback = args[args.length - 1],
                events = kendo.applyEventMap(args[0], ns);

                // setup mouse trap
                if (support.mouseAndTouchPresent && events.search(/mouse|click/) > -1 && this[0] !== document.documentElement) {
                    MouseEventNormalizer.setupMouseMute();

                    var selector = args.length === 2 ? null : args[1],
                    bustClick = events.indexOf("click") > -1 && events.indexOf("touchend") > -1;

                    on.call(this,
                    {
                        touchstart: MouseEventNormalizer.muteMouse,
                        touchend: MouseEventNormalizer.unMuteMouse
                    },
                    selector,
                    {
                        bustClick: bustClick
                    });
                }

                if (typeof callback === STRING) {
                    context = that.data("handler");
                    callback = context[callback];

                    args[args.length - 1] = function (e) {
                        callback.call(context, e);
                    };
                }

                args[0] = events;

                on.apply(that, args);

                return that;
            },

            kendoDestroy: function (ns) {
                ns = ns || this.data("kendoNS");

                if (ns) {
                    this.off("." + ns);
                }

                return this;
            }
        });

        kendo.jQuery = kendoJQuery;
        kendo.eventMap = eventMap;

        kendo.timezone = (function () {
            var months = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 };
            var days = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };

            function ruleToDate(year, rule) {
                var date;
                var targetDay;
                var ourDay;
                var month = rule[3];
                var on = rule[4];
                var time = rule[5];
                var cache = rule[8];

                if (!cache) {
                    rule[8] = cache = {};
                }

                if (cache[year]) {
                    return cache[year];
                }

                if (!isNaN(on)) {
                    date = new Date(Date.UTC(year, months[month], on, time[0], time[1], time[2], 0));
                } else if (on.indexOf("last") === 0) {
                    date = new Date(Date.UTC(year, months[month] + 1, 1, time[0] - 24, time[1], time[2], 0));

                    targetDay = days[on.substr(4, 3)];
                    ourDay = date.getUTCDay();

                    date.setUTCDate(date.getUTCDate() + targetDay - ourDay - (targetDay > ourDay ? 7 : 0));
                } else if (on.indexOf(">=") >= 0) {
                    date = new Date(Date.UTC(year, months[month], on.substr(5), time[0], time[1], time[2], 0));

                    targetDay = days[on.substr(0, 3)];
                    ourDay = date.getUTCDay();

                    date.setUTCDate(date.getUTCDate() + targetDay - ourDay + (targetDay < ourDay ? 7 : 0));
                }

                return cache[year] = date;
            }

            function findRule(utcTime, rules, zone) {
                rules = rules[zone];

                if (!rules) {
                    var time = zone.split(":");
                    var offset = 0;

                    if (time.length > 1) {
                        offset = time[0] * 60 + Number(time[1]);
                    }

                    return [-1000000, 'max', '-', 'Jan', 1, [0, 0, 0], offset, '-'];
                }

                var year = new Date(utcTime).getUTCFullYear();

                rules = jQuery.grep(rules, function (rule) {
                    var from = rule[0];
                    var to = rule[1];

                    return from <= year && (to >= year || (from == year && to == "only") || to == "max");
                });

                rules.push(utcTime);

                rules.sort(function (a, b) {
                    if (typeof a != "number") {
                        a = Number(ruleToDate(year, a));
                    }

                    if (typeof b != "number") {
                        b = Number(ruleToDate(year, b));
                    }

                    return a - b;
                });

                var rule = rules[jQuery.inArray(utcTime, rules) - 1] || rules[rules.length - 1];

                return isNaN(rule) ? rule : null;
            }

            function findZone(utcTime, zones, timezone) {
                var zoneRules = zones[timezone];

                if (typeof zoneRules === "string") {
                    zoneRules = zones[zoneRules];
                }

                if (!zoneRules) {
                    throw new Error('Timezone "' + timezone + '" is either incorrect, or kendo.timezones.min.js is not included.');
                }

                for (var idx = zoneRules.length - 1; idx >= 0; idx--) {
                    var until = zoneRules[idx][3];

                    if (until && utcTime > until) {
                        break;
                    }
                }

                var zone = zoneRules[idx + 1];

                if (!zone) {
                    throw new Error('Timezone "' + timezone + '" not found on ' + utcTime + ".");
                }

                return zone;
            }

            function zoneAndRule(utcTime, zones, rules, timezone) {
                if (typeof utcTime != NUMBER) {
                    utcTime = Date.UTC(utcTime.getFullYear(), utcTime.getMonth(),
                    utcTime.getDate(), utcTime.getHours(), utcTime.getMinutes(),
                    utcTime.getSeconds(), utcTime.getMilliseconds());
                }

                var zone = findZone(utcTime, zones, timezone);

                return {
                    zone: zone,
                    rule: findRule(utcTime, rules, zone[1])
                };
            }

            function offset(utcTime, timezone) {
                if (timezone == "Etc/UTC" || timezone == "Etc/GMT") {
                    return 0;
                }

                var info = zoneAndRule(utcTime, this.zones, this.rules, timezone);
                var zone = info.zone;
                var rule = info.rule;

                return kendo.parseFloat(rule ? zone[0] - rule[6] : zone[0]);
            }

            function abbr(utcTime, timezone) {
                var info = zoneAndRule(utcTime, this.zones, this.rules, timezone);
                var zone = info.zone;
                var rule = info.rule;

                var base = zone[2];

                if (base.indexOf("/") >= 0) {
                    return base.split("/")[rule && +rule[6] ? 1 : 0];
                } else if (base.indexOf("%s") >= 0) {
                    return base.replace("%s", (!rule || rule[7] == "-") ? '' : rule[7]);
                }

                return base;
            }

            function convert(date, fromOffset, toOffset) {
                if (typeof fromOffset == STRING) {
                    fromOffset = this.offset(date, fromOffset);
                }

                if (typeof toOffset == STRING) {
                    toOffset = this.offset(date, toOffset);
                }

                var fromLocalOffset = date.getTimezoneOffset();

                date = new Date(date.getTime() + (fromOffset - toOffset) * 60000);

                var toLocalOffset = date.getTimezoneOffset();

                return new Date(date.getTime() + (toLocalOffset - fromLocalOffset) * 60000);
            }

            function apply(date, timezone) {
                return this.convert(date, date.getTimezoneOffset(), timezone);
            }

            function remove(date, timezone) {
                return this.convert(date, timezone, date.getTimezoneOffset());
            }

            function toLocalDate(time) {
                return this.apply(new Date(time), "Etc/UTC");
            }

            return {
                zones: {},
                rules: {},
                offset: offset,
                convert: convert,
                apply: apply,
                remove: remove,
                abbr: abbr,
                toLocalDate: toLocalDate
            };
        })();

        kendo.date = (function () {
            var MS_PER_MINUTE = 60000,
            MS_PER_DAY = 86400000;

            function adjustDST(date, hours) {
                if (hours === 0 && date.getHours() === 23) {
                    date.setHours(date.getHours() + 2);
                    return true;
                }

                return false;
            }

            function setDayOfWeek(date, day, dir) {
                var hours = date.getHours();

                dir = dir || 1;
                day = ((day - date.getDay()) + (7 * dir)) % 7;

                date.setDate(date.getDate() + day);
                adjustDST(date, hours);
            }

            function dayOfWeek(date, day, dir) {
                date = new Date(date);
                setDayOfWeek(date, day, dir);
                return date;
            }

            function firstDayOfMonth(date) {
                return new Date(
                date.getFullYear(),
                date.getMonth(),
                1
            );
            }

            function lastDayOfMonth(date) {
                var last = new Date(date.getFullYear(), date.getMonth() + 1, 0),
                first = firstDayOfMonth(date),
                timeOffset = Math.abs(last.getTimezoneOffset() - first.getTimezoneOffset());

                if (timeOffset) {
                    last.setHours(first.getHours() + (timeOffset / 60));
                }

                return last;
            }

            function getDate(date) {
                date = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
                adjustDST(date, 0);
                return date;
            }

            function toUtcTime(date) {
                return Date.UTC(date.getFullYear(), date.getMonth(),
                        date.getDate(), date.getHours(), date.getMinutes(),
                        date.getSeconds(), date.getMilliseconds());
            }

            function getMilliseconds(date) {
                return date.getTime() - getDate(date);
            }

            function isInTimeRange(value, min, max) {
                var msMin = getMilliseconds(min),
                msMax = getMilliseconds(max),
                msValue;

                if (!value || msMin == msMax) {
                    return true;
                }

                if (min >= max) {
                    max += MS_PER_DAY;
                }

                msValue = getMilliseconds(value);

                if (msMin > msValue) {
                    msValue += MS_PER_DAY;
                }

                if (msMax < msMin) {
                    msMax += MS_PER_DAY;
                }

                return msValue >= msMin && msValue <= msMax;
            }

            function isInDateRange(value, min, max) {
                var msMin = min.getTime(),
                msMax = max.getTime(),
                msValue;

                if (msMin >= msMax) {
                    msMax += MS_PER_DAY;
                }

                msValue = value.getTime();

                return msValue >= msMin && msValue <= msMax;
            }

            function addDays(date, offset) {
                var hours = date.getHours();
                date = new Date(date);

                setTime(date, offset * MS_PER_DAY);
                adjustDST(date, hours);
                return date;
            }

            function setTime(date, milliseconds, ignoreDST) {
                var offset = date.getTimezoneOffset();
                var difference;

                date.setTime(date.getTime() + milliseconds);

                if (!ignoreDST) {
                    difference = date.getTimezoneOffset() - offset;
                    date.setTime(date.getTime() + difference * MS_PER_MINUTE);
                }
            }

            function today() {
                return getDate(new Date());
            }

            function isToday(date) {
                return getDate(date).getTime() == today().getTime();
            }

            function toInvariantTime(date) {
                var staticDate = new Date(1980, 1, 1, 0, 0, 0);

                if (date) {
                    staticDate.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
                }

                return staticDate;
            }

            return {
                adjustDST: adjustDST,
                dayOfWeek: dayOfWeek,
                setDayOfWeek: setDayOfWeek,
                getDate: getDate,
                isInDateRange: isInDateRange,
                isInTimeRange: isInTimeRange,
                isToday: isToday,
                nextDay: function (date) {
                    return addDays(date, 1);
                },
                previousDay: function (date) {
                    return addDays(date, -1);
                },
                toUtcTime: toUtcTime,
                MS_PER_DAY: MS_PER_DAY,
                MS_PER_HOUR: 60 * MS_PER_MINUTE,
                MS_PER_MINUTE: MS_PER_MINUTE,
                setTime: setTime,
                addDays: addDays,
                today: today,
                toInvariantTime: toInvariantTime,
                firstDayOfMonth: firstDayOfMonth,
                lastDayOfMonth: lastDayOfMonth,
                getMilliseconds: getMilliseconds
                //TODO methods: combine date portion and time portion from arguments - date1, date 2
            };
        })();


        kendo.stripWhitespace = function (element) {
            if (document.createNodeIterator) {
                var iterator = document.createNodeIterator(element, NodeFilter.SHOW_TEXT, function (node) {
                    return node.parentNode == element ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
                }, false);

                while (iterator.nextNode()) {
                    if (iterator.referenceNode && !iterator.referenceNode.textContent.trim()) {
                        iterator.referenceNode.parentNode.removeChild(iterator.referenceNode);
                    }
                }
            } else { // IE7/8 support
                for (var i = 0; i < element.childNodes.length; i++) {
                    var child = element.childNodes[i];

                    if (child.nodeType == 3 && !/\S/.test(child.nodeValue)) {
                        element.removeChild(child);
                        i--;
                    }

                    if (child.nodeType == 1) {
                        kendo.stripWhitespace(child);
                    }
                }
            }
        };

        var animationFrame = window.requestAnimationFrame ||
                          window.webkitRequestAnimationFrame ||
                          window.mozRequestAnimationFrame ||
                          window.oRequestAnimationFrame ||
                          window.msRequestAnimationFrame ||
                          function (callback) { setTimeout(callback, 1000 / 60); };

        kendo.animationFrame = function (callback) {
            animationFrame.call(window, callback);
        };

        var animationQueue = [];

        kendo.queueAnimation = function (callback) {
            animationQueue[animationQueue.length] = callback;
            if (animationQueue.length === 1) {
                kendo.runNextAnimation();
            }
        };

        kendo.runNextAnimation = function () {
            kendo.animationFrame(function () {
                if (animationQueue[0]) {
                    animationQueue.shift()();
                    if (animationQueue[0]) {
                        kendo.runNextAnimation();
                    }
                }
            });
        };

        kendo.parseQueryStringParams = function (url) {
            var queryString = url.split('?')[1] || "",
            params = {},
            paramParts = queryString.split(/&|=/),
            length = paramParts.length,
            idx = 0;

            for (; idx < length; idx += 2) {
                if (paramParts[idx] !== "") {
                    params[decodeURIComponent(paramParts[idx])] = decodeURIComponent(paramParts[idx + 1]);
                }
            }

            return params;
        };

        kendo.elementUnderCursor = function (e) {
            return document.elementFromPoint(e.x.client, e.y.client);
        };

        kendo.wheelDeltaY = function (jQueryEvent) {
            var e = jQueryEvent.originalEvent,
            deltaY = e.wheelDeltaY,
            delta;

            if (e.wheelDelta) { // Webkit and IE
                if (deltaY === undefined || deltaY) { // IE does not have deltaY, thus always scroll (horizontal scrolling is treated as vertical)
                    delta = e.wheelDelta;
                }
            } else if (e.detail && e.axis === e.VERTICAL_AXIS) { // Firefox and Opera
                delta = (-e.detail) * 10;
            }

            return delta;
        };

        kendo.throttle = function (fn, delay) {
            var timeout;
            var lastExecTime = 0;

            if (!delay || delay <= 0) {
                return fn;
            }

            return function () {
                var that = this;
                var elapsed = +new Date() - lastExecTime;
                var args = arguments;

                function exec() {
                    fn.apply(that, args);
                    lastExecTime = +new Date();
                }

                // first execution
                if (!lastExecTime) {
                    return exec();
                }

                if (timeout) {
                    clearTimeout(timeout);
                }

                if (elapsed > delay) {
                    exec();
                } else {
                    timeout = setTimeout(exec, delay - elapsed);
                }
            };
        };


        kendo.caret = function (element, start, end) {
            var rangeElement;
            var isPosition = start !== undefined;

            if (end === undefined) {
                end = start;
            }

            if (element[0]) {
                element = element[0];
            }

            if (isPosition && element.disabled) {
                return;
            }

            try {
                if (element.selectionStart !== undefined) {
                    if (isPosition) {
                        element.focus();
                        element.setSelectionRange(start, end);
                    } else {
                        start = [element.selectionStart, element.selectionEnd];
                    }
                } else if (document.selection) {
                    if ($(element).is(":visible")) {
                        element.focus();
                    }

                    rangeElement = element.createTextRange();

                    if (isPosition) {
                        rangeElement.collapse(true);
                        rangeElement.moveStart("character", start);
                        rangeElement.moveEnd("character", end - start);
                        rangeElement.select();
                    } else {
                        var rangeDuplicated = rangeElement.duplicate(),
                        selectionStart, selectionEnd;

                        rangeElement.moveToBookmark(document.selection.createRange().getBookmark());
                        rangeDuplicated.setEndPoint('EndToStart', rangeElement);
                        selectionStart = rangeDuplicated.text.length;
                        selectionEnd = selectionStart + rangeElement.text.length;

                        start = [selectionStart, selectionEnd];
                    }
                }
            } catch (e) {
                /* element is not focused or it is not in the DOM */
                start = [];
            }

            return start;
        };

        kendo.compileMobileDirective = function (element, scopeSetup) {
            var angular = window.angular;

            element.attr("data-" + kendo.ns + "role", element[0].tagName.toLowerCase().replace('kendo-mobile-', '').replace('-', ''));

            angular.element(element).injector().invoke(["$compile", function ($compile) {
                var scope = angular.element(element).scope();
                if (scopeSetup) {
                    scopeSetup(scope);
                }
                $compile(element)(scope);
                scope.$digest();
            } ]);

            return kendo.widgetInstance(element, kendo.mobile.ui);
        };

        // kendo.saveAs -----------------------------------------------
        (function () {
            function postToProxy(dataURI, fileName, proxyURL) {
                var form = $("<form>").attr({
                    action: proxyURL,
                    method: "POST"
                });

                var parts = dataURI.split(";base64,");

                $('<input>').attr({
                    value: parts[0].replace("data:", ""),
                    name: "contentType",
                    type: "hidden"
                }).appendTo(form);

                $('<input>').attr({
                    value: parts[1],
                    name: "base64",
                    type: "hidden"
                }).appendTo(form);

                $('<input>').attr({
                    value: fileName,
                    name: "fileName",
                    type: "hidden"
                }).appendTo(form);

                form.appendTo("body").submit().remove();
            }

            var fileSaver = document.createElement("a");
            var downloadAttribute = "download" in fileSaver;

            function saveAsBlob(dataURI, fileName) {
                var blob = dataURI; // could be a Blob object

                if (typeof dataURI == "string") {
                    var parts = dataURI.split(";base64,");
                    var contentType = parts[0];
                    var base64 = atob(parts[1]);
                    var array = new Uint8Array(base64.length);

                    for (var idx = 0; idx < base64.length; idx++) {
                        array[idx] = base64.charCodeAt(idx);
                    }
                    blob = new Blob([array.buffer], { type: contentType });
                }

                navigator.msSaveBlob(blob, fileName);
            }

            function saveAsDataURI(dataURI, fileName) {
                if (window.Blob && dataURI instanceof Blob) {
                    dataURI = URL.createObjectURL(dataURI);
                }

                fileSaver.download = fileName;
                fileSaver.href = dataURI;

                var e = document.createEvent("MouseEvents");
                e.initMouseEvent("click", true, false, window,
                0, 0, 0, 0, 0, false, false, false, false, 0, null);

                fileSaver.dispatchEvent(e);
            }

            kendo.saveAs = function (options) {
                var save = postToProxy;

                if (!options.forceProxy) {
                    if (downloadAttribute) {
                        save = saveAsDataURI;
                    } else if (navigator.msSaveBlob) {
                        save = saveAsBlob;
                    }
                }

                save(options.dataURI, options.fileName, options.proxyURL);
            };
        })();
    })(jQuery, window);





    (function ($, undefined) {
        var kendo = window.kendo,
        CHANGE = "change",
        BACK = "back",
        SAME = "same",
        support = kendo.support,
        location = window.location,
        history = window.history,
        CHECK_URL_INTERVAL = 50,
        BROKEN_BACK_NAV = kendo.support.browser.msie,
        hashStrip = /^#*/,
        document = window.document;

        function absoluteURL(path, pathPrefix) {
            if (!pathPrefix) {
                return path;
            }

            if (path + "/" === pathPrefix) {
                path = pathPrefix;
            }

            var regEx = new RegExp("^" + pathPrefix, "i");

            if (!regEx.test(path)) {
                path = pathPrefix + "/" + path;
            }

            return location.protocol + '//' + (location.host + "/" + path).replace(/\/\/+/g, '/');
        }

        function hashDelimiter(bang) {
            return bang ? "#!" : "#";
        }

        function locationHash(hashDelimiter) {
            var href = location.href;

            // ignore normal anchors if in hashbang mode - however, still return "" if no hash present
            if (hashDelimiter === "#!" && href.indexOf("#") > -1 && href.indexOf("#!") < 0) {
                return null;
            }

            return href.split(hashDelimiter)[1] || "";
        }

        function stripRoot(root, url) {
            if (url.indexOf(root) === 0) {
                return (url.substr(root.length)).replace(/\/\//g, '/');
            } else {
                return url;
            }
        }

        var HistoryAdapter = kendo.Class.extend({
            back: function () {
                if (BROKEN_BACK_NAV) {
                    setTimeout(function () { history.back(); });
                } else {
                    history.back();
                }
            },

            forward: function () {
                if (BROKEN_BACK_NAV) {
                    setTimeout(function () { history.forward(); });
                } else {
                    history.forward();
                }
            },

            length: function () {
                return history.length;
            },

            replaceLocation: function (url) {
                location.replace(url);
            }
        });

        var PushStateAdapter = HistoryAdapter.extend({
            init: function (root) {
                this.root = root;
            },

            navigate: function (to) {
                history.pushState({}, document.title, absoluteURL(to, this.root));
            },

            replace: function (to) {
                history.replaceState({}, document.title, absoluteURL(to, this.root));
            },

            normalize: function (url) {
                return stripRoot(this.root, url);
            },

            current: function () {
                var current = location.pathname;

                if (location.search) {
                    current += location.search;
                }

                return stripRoot(this.root, current);
            },

            change: function (callback) {
                $(window).bind("popstate.kendo", callback);
            },

            stop: function () {
                $(window).unbind("popstate.kendo");
            },

            normalizeCurrent: function (options) {
                var fixedUrl,
                root = options.root,
                pathname = location.pathname,
                hash = locationHash(hashDelimiter(options.hashBang));

                if (root === pathname + "/") {
                    fixedUrl = root;
                }

                if (root === pathname && hash) {
                    fixedUrl = absoluteURL(hash.replace(hashStrip, ''), root);
                }

                if (fixedUrl) {
                    history.pushState({}, document.title, fixedUrl);
                }
            }
        });

        function fixHash(url) {
            return url.replace(/^(#)?/, "#");
        }

        function fixBang(url) {
            return url.replace(/^(#(!)?)?/, "#!");
        }

        var HashAdapter = HistoryAdapter.extend({
            init: function (bang) {
                this._id = kendo.guid();
                this.prefix = hashDelimiter(bang);
                this.fix = bang ? fixBang : fixHash;
            },

            navigate: function (to) {
                location.hash = this.fix(to);
            },

            replace: function (to) {
                this.replaceLocation(this.fix(to));
            },

            normalize: function (url) {
                if (url.indexOf(this.prefix) < 0) {
                    return url;
                } else {
                    return url.split(this.prefix)[1];
                }
            },

            change: function (callback) {
                if (support.hashChange) {
                    $(window).on("hashchange." + this._id, callback);
                } else {
                    this._interval = setInterval(callback, CHECK_URL_INTERVAL);
                }
            },

            stop: function () {
                $(window).off("hashchange." + this._id);
                clearInterval(this._interval);
            },

            current: function () {
                return locationHash(this.prefix);
            },

            normalizeCurrent: function (options) {
                var pathname = location.pathname,
                root = options.root;

                if (options.pushState && root !== pathname) {
                    this.replaceLocation(root + this.prefix + stripRoot(root, pathname));
                    return true; // browser will reload at this point.
                }

                return false;
            }
        });

        var History = kendo.Observable.extend({
            start: function (options) {
                options = options || {};

                this.bind([CHANGE, BACK, SAME], options);

                if (this._started) {
                    return;
                }

                this._started = true;

                options.root = options.root || "/";

                var adapter = this.createAdapter(options),
                current;

                // adapter may reload the document
                if (adapter.normalizeCurrent(options)) {
                    return;
                }

                current = adapter.current();

                $.extend(this, {
                    adapter: adapter,
                    root: options.root,
                    historyLength: adapter.length(),
                    current: current,
                    locations: [current]
                });

                adapter.change($.proxy(this, "_checkUrl"));
            },

            createAdapter: function (options) {
                return support.pushState && options.pushState ? new PushStateAdapter(options.root) : new HashAdapter(options.hashBang);
            },

            stop: function () {
                if (!this._started) {
                    return;
                }
                this.adapter.stop();
                this.unbind(CHANGE);
                this._started = false;
            },

            change: function (callback) {
                this.bind(CHANGE, callback);
            },

            replace: function (to, silent) {

                this._navigate(to, silent, function (adapter) {
                    adapter.replace(to);
                    this.locations[this.locations.length - 1] = this.current;
                });
            },

            navigate: function (to, silent) {
                if (to === "#:back") {
                    this.backCalled = true;
                    this.adapter.back();
                    return;
                }

                this._navigate(to, silent, function (adapter) {
                    adapter.navigate(to);
                    this.locations.push(this.current);
                });
            },

            _navigate: function (to, silent, callback) {
                var adapter = this.adapter;

                to = adapter.normalize(to);

                if (this.current === to || this.current === decodeURIComponent(to)) {
                    this.trigger(SAME);
                    return;
                }

                if (!silent) {
                    if (this.trigger(CHANGE, { url: to })) {
                        return;
                    }
                }

                this.current = to;

                callback.call(this, adapter);

                this.historyLength = adapter.length();
            },

            _checkUrl: function () {
                var adapter = this.adapter,
                current = adapter.current(),
                newLength = adapter.length(),
                navigatingInExisting = this.historyLength === newLength,
                back = current === this.locations[this.locations.length - 2] && navigatingInExisting,
                backCalled = this.backCalled,
                prev = this.current;

                if (current === null || this.current === current || this.current === decodeURIComponent(current)) {
                    return true;
                }

                this.historyLength = newLength;
                this.backCalled = false;

                this.current = current;

                if (back && this.trigger("back", { url: prev, to: current })) {
                    adapter.forward();
                    this.current = prev;
                    return;
                }

                if (this.trigger(CHANGE, { url: current, backButtonPressed: !backCalled })) {
                    if (back) {
                        adapter.forward();
                    } else {
                        adapter.back();
                        this.historyLength--;
                    }
                    this.current = prev;
                    return;
                }

                if (back) {
                    this.locations.pop();
                } else {
                    this.locations.push(current);
                }
            }
        });

        kendo.History = History;
        kendo.History.HistoryAdapter = HistoryAdapter;
        kendo.History.HashAdapter = HashAdapter;
        kendo.History.PushStateAdapter = PushStateAdapter;
        kendo.absoluteURL = absoluteURL;
        kendo.history = new History();
    })(window.kendo.jQuery);

    (function () {
        var kendo = window.kendo,
        history = kendo.history,
        Observable = kendo.Observable,
        INIT = "init",
        ROUTE_MISSING = "routeMissing",
        CHANGE = "change",
        BACK = "back",
        SAME = "same",
        optionalParam = /\((.*?)\)/g,
        namedParam = /(\(\?)?:\w+/g,
        splatParam = /\*\w+/g,
        escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;

        function namedParamReplace(match, optional) {
            return optional ? match : '([^\/]+)';
        }

        function routeToRegExp(route, ignoreCase) {
            return new RegExp('^' + route
            .replace(escapeRegExp, '\\$&')
            .replace(optionalParam, '(?:$1)?')
            .replace(namedParam, namedParamReplace)
            .replace(splatParam, '(.*?)') + '$', ignoreCase ? "i" : "");
        }

        function stripUrl(url) {
            return url.replace(/(\?.*)|(#.*)/g, "");
        }

        var Route = kendo.Class.extend({
            init: function (route, callback, ignoreCase) {
                if (!(route instanceof RegExp)) {
                    route = routeToRegExp(route, ignoreCase);
                }

                this.route = route;
                this._callback = callback;
            },

            callback: function (url) {
                var params,
                idx = 0,
                length,
                queryStringParams = kendo.parseQueryStringParams(url);

                url = stripUrl(url);
                params = this.route.exec(url).slice(1);
                length = params.length;

                for (; idx < length; idx++) {
                    if (typeof params[idx] !== 'undefined') {
                        params[idx] = decodeURIComponent(params[idx]);
                    }
                }

                params.push(queryStringParams);

                this._callback.apply(null, params);
            },

            worksWith: function (url) {
                if (this.route.test(stripUrl(url))) {
                    this.callback(url);
                    return true;
                } else {
                    return false;
                }
            }
        });

        var Router = Observable.extend({
            init: function (options) {
                if (!options) {
                    options = {};
                }

                Observable.fn.init.call(this);

                this.routes = [];
                this.pushState = options.pushState;
                this.hashBang = options.hashBang;
                this.root = options.root;
                this.ignoreCase = options.ignoreCase !== false;

                this.bind([INIT, ROUTE_MISSING, CHANGE, SAME], options);
            },

            destroy: function () {
                history.unbind(CHANGE, this._urlChangedProxy);
                history.unbind(SAME, this._sameProxy);
                history.unbind(BACK, this._backProxy);
                this.unbind();
            },

            start: function () {
                var that = this,
                sameProxy = function () { that._same(); },
                backProxy = function (e) { that._back(e); },
                urlChangedProxy = function (e) { that._urlChanged(e); };

                history.start({
                    same: sameProxy,
                    change: urlChangedProxy,
                    back: backProxy,
                    pushState: that.pushState,
                    hashBang: that.hashBang,
                    root: that.root
                });

                var initEventObject = { url: history.current || "/", preventDefault: $.noop };

                if (!that.trigger(INIT, initEventObject)) {
                    that._urlChanged(initEventObject);
                }

                this._urlChangedProxy = urlChangedProxy;
                this._backProxy = backProxy;
            },

            route: function (route, callback) {
                this.routes.push(new Route(route, callback, this.ignoreCase));
            },

            navigate: function (url, silent) {
                kendo.history.navigate(url, silent);
            },

            replace: function (url, silent) {
                kendo.history.replace(url, silent);
            },

            _back: function (e) {
                if (this.trigger(BACK, { url: e.url, to: e.to })) {
                    e.preventDefault();
                }
            },

            _same: function (e) {
                this.trigger(SAME);
            },

            _urlChanged: function (e) {
                var url = e.url;

                if (!url) {
                    url = "/";
                }

                if (this.trigger(CHANGE, { url: e.url, params: kendo.parseQueryStringParams(e.url), backButtonPressed: e.backButtonPressed })) {
                    e.preventDefault();
                    return;
                }

                var idx = 0,
                routes = this.routes,
                route,
                length = routes.length;

                for (; idx < length; idx++) {
                    route = routes[idx];

                    if (route.worksWith(url)) {
                        return;
                    }
                }

                if (this.trigger(ROUTE_MISSING, { url: url, params: kendo.parseQueryStringParams(url), backButtonPressed: e.backButtonPressed })) {
                    e.preventDefault();
                }
            }
        });

        kendo.Router = Router;
    })();





    (function ($, undefined) {
        var kendo = window.kendo,
        extend = $.extend,
        odataFilters = {
            eq: "eq",
            neq: "ne",
            gt: "gt",
            gte: "ge",
            lt: "lt",
            lte: "le",
            contains: "substringof",
            doesnotcontain: "substringof",
            endswith: "endswith",
            startswith: "startswith"
        },
        odataFiltersVersionFour = extend({}, odataFilters, {
            contains: "contains"
        }),
        mappers = {
            pageSize: $.noop,
            page: $.noop,
            filter: function (params, filter, useVersionFour) {
                if (filter) {
                    params.$filter = toOdataFilter(filter, useVersionFour);
                }
            },
            sort: function (params, orderby) {
                var expr = $.map(orderby, function (value) {
                    var order = value.field.replace(/\./g, "/");

                    if (value.dir === "desc") {
                        order += " desc";
                    }

                    return order;
                }).join(",");

                if (expr) {
                    params.$orderby = expr;
                }
            },
            skip: function (params, skip) {
                if (skip) {
                    params.$skip = skip;
                }
            },
            take: function (params, take) {
                if (take) {
                    params.$top = take;
                }
            }
        },
        defaultDataType = {
            read: {
                dataType: "jsonp"
            }
        };

        function toOdataFilter(filter, useOdataFour) {
            var result = [],
            logic = filter.logic || "and",
            idx,
            length,
            field,
            type,
            format,
            operator,
            value,
            ignoreCase,
            filters = filter.filters;

            for (idx = 0, length = filters.length; idx < length; idx++) {
                filter = filters[idx];
                field = filter.field;
                value = filter.value;
                operator = filter.operator;

                if (filter.filters) {
                    filter = toOdataFilter(filter, useOdataFour);
                } else {
                    ignoreCase = filter.ignoreCase;
                    field = field.replace(/\./g, "/");
                    filter = odataFilters[operator];
                    if (useOdataFour) {
                        filter = odataFiltersVersionFour[operator];
                    }

                    if (filter && value !== undefined) {
                        type = $.type(value);
                        if (type === "string") {
                            format = "'{1}'";
                            value = value.replace(/'/g, "''");

                            if (ignoreCase === true) {
                                field = "tolower(" + field + ")";
                            }

                        } else if (type === "date") {
                            if (useOdataFour) {
                                format = "{1:yyyy-MM-ddTHH:mm:ss+00:00}";
                            } else {
                                format = "datetime'{1:yyyy-MM-ddTHH:mm:ss}'";
                            }
                        } else {
                            format = "{1}";
                        }

                        if (filter.length > 3) {
                            if (filter !== "substringof") {
                                format = "{0}({2}," + format + ")";
                            } else {
                                format = "{0}(" + format + ",{2})";
                                if (operator === "doesnotcontain") {
                                    if (useOdataFour) {
                                        format = "{0}({2},'{1}') eq -1";
                                        filter = "indexof";
                                    } else {
                                        format += " eq false";
                                    }
                                }
                            }
                        } else {
                            format = "{2} {0} " + format;
                        }

                        filter = kendo.format(format, filter, value, field);
                    }
                }

                result.push(filter);
            }

            filter = result.join(" " + logic + " ");

            if (result.length > 1) {
                filter = "(" + filter + ")";
            }

            return filter;
        }

        extend(true, kendo.data, {
            schemas: {
                odata: {
                    type: "json",
                    data: function (data) {
                        return data.d.results || [data.d];
                    },
                    total: "d.__count"
                }
            },
            transports: {
                odata: {
                    read: {
                        cache: true, // to prevent jQuery from adding cache buster
                        dataType: "jsonp",
                        jsonp: "$callback"
                    },
                    update: {
                        cache: true,
                        dataType: "json",
                        contentType: "application/json", // to inform the server the the request body is JSON encoded
                        type: "PUT" // can be PUT or MERGE
                    },
                    create: {
                        cache: true,
                        dataType: "json",
                        contentType: "application/json",
                        type: "POST" // must be POST to create new entity
                    },
                    destroy: {
                        cache: true,
                        dataType: "json",
                        type: "DELETE"
                    },
                    parameterMap: function (options, type, useVersionFour) {
                        var params,
                        value,
                        option,
                        dataType;

                        options = options || {};
                        type = type || "read";
                        dataType = (this.options || defaultDataType)[type];
                        dataType = dataType ? dataType.dataType : "json";

                        if (type === "read") {
                            params = {
                                $inlinecount: "allpages"
                            };

                            if (dataType != "json") {
                                params.$format = "json";
                            }

                            for (option in options) {
                                if (mappers[option]) {
                                    mappers[option](params, options[option], useVersionFour);
                                } else {
                                    params[option] = options[option];
                                }
                            }
                        } else {
                            if (dataType !== "json") {
                                throw new Error("Only json dataType can be used for " + type + " operation.");
                            }

                            if (type !== "destroy") {
                                for (option in options) {
                                    value = options[option];
                                    if (typeof value === "number") {
                                        options[option] = value + "";
                                    }
                                }

                                params = kendo.stringify(options);
                            }
                        }

                        return params;
                    }
                }
            }
        });

        extend(true, kendo.data, {
            schemas: {
                "odata-v4": {
                    type: "json",
                    data: function (data) {
                        if (data.value) {
                            return data.value;
                        }
                        delete data["odata.metadata"];
                        return [data];
                    },
                    total: function (data) {
                        return data["@odata.count"];
                    }
                }
            },
            transports: {
                "odata-v4": {
                    read: {
                        cache: true, // to prevent jQuery from adding cache buster
                        dataType: "json"
                    },
                    update: {
                        cache: true,
                        dataType: "json",
                        contentType: "application/json;IEEE754Compatible=true", // to inform the server the the request body is JSON encoded
                        type: "PUT" // can be PUT or MERGE
                    },
                    create: {
                        cache: true,
                        dataType: "json",
                        contentType: "application/json;IEEE754Compatible=true",
                        type: "POST" // must be POST to create new entity
                    },
                    destroy: {
                        cache: true,
                        dataType: "json",
                        type: "DELETE"
                    },
                    parameterMap: function (options, type) {
                        var result = kendo.data.transports.odata.parameterMap(options, type, true);
                        if (type == "read") {
                            result.$count = true;
                            delete result.$inlinecount;
                        }

                        return result;
                    }
                }
            }
        });

    })(window.kendo.jQuery);





    /*jshint  eqnull: true, boss: true */
    (function ($, undefined) {
        var kendo = window.kendo,
        isArray = $.isArray,
        isPlainObject = $.isPlainObject,
        map = $.map,
        each = $.each,
        extend = $.extend,
        getter = kendo.getter,
        Class = kendo.Class;

        var XmlDataReader = Class.extend({
            init: function (options) {
                var that = this,
                total = options.total,
                model = options.model,
                parse = options.parse,
                errors = options.errors,
                serialize = options.serialize,
                data = options.data;

                if (model) {
                    if (isPlainObject(model)) {
                        var base = options.modelBase || kendo.data.Model;

                        if (model.fields) {
                            each(model.fields, function (field, value) {
                                if (isPlainObject(value) && value.field) {
                                    value = extend(value, { field: that.getter(value.field) });
                                } else {
                                    value = { field: that.getter(value) };
                                }
                                model.fields[field] = value;
                            });
                        }

                        var id = model.id;
                        if (id) {
                            var idField = {};

                            idField[that.xpathToMember(id, true)] = { field: that.getter(id) };
                            model.fields = extend(idField, model.fields);
                            model.id = that.xpathToMember(id);
                        }
                        model = base.define(model);
                    }

                    that.model = model;
                }

                if (total) {
                    if (typeof total == "string") {
                        total = that.getter(total);
                        that.total = function (data) {
                            return parseInt(total(data), 10);
                        };
                    } else if (typeof total == "function") {
                        that.total = total;
                    }
                }

                if (errors) {
                    if (typeof errors == "string") {
                        errors = that.getter(errors);
                        that.errors = function (data) {
                            return errors(data) || null;
                        };
                    } else if (typeof errors == "function") {
                        that.errors = errors;
                    }
                }

                if (data) {
                    if (typeof data == "string") {
                        data = that.xpathToMember(data);
                        that.data = function (value) {
                            var result = that.evaluate(value, data),
                            modelInstance;

                            result = isArray(result) ? result : [result];

                            if (that.model && model.fields) {
                                modelInstance = new that.model();

                                return map(result, function (value) {
                                    if (value) {
                                        var record = {}, field;

                                        for (field in model.fields) {
                                            record[field] = modelInstance._parse(field, model.fields[field].field(value));
                                        }

                                        return record;
                                    }
                                });
                            }

                            return result;
                        };
                    } else if (typeof data == "function") {
                        that.data = data;
                    }
                }

                if (typeof parse == "function") {
                    var xmlParse = that.parse;

                    that.parse = function (data) {
                        var xml = parse.call(that, data);
                        return xmlParse.call(that, xml);
                    };
                }

                if (typeof serialize == "function") {
                    that.serialize = serialize;
                }
            },
            total: function (result) {
                return this.data(result).length;
            },
            errors: function (data) {
                return data ? data.errors : null;
            },
            serialize: function (data) {
                return data;
            },
            parseDOM: function (element) {
                var result = {},
                parsedNode,
                node,
                nodeType,
                nodeName,
                member,
                attribute,
                attributes = element.attributes,
                attributeCount = attributes.length,
                idx;

                for (idx = 0; idx < attributeCount; idx++) {
                    attribute = attributes[idx];
                    result["@" + attribute.nodeName] = attribute.nodeValue;
                }

                for (node = element.firstChild; node; node = node.nextSibling) {
                    nodeType = node.nodeType;

                    if (nodeType === 3 || nodeType === 4) {
                        // text nodes or CDATA are stored as #text field
                        result["#text"] = node.nodeValue;
                    } else if (nodeType === 1) {
                        // elements are stored as fields
                        parsedNode = this.parseDOM(node);

                        nodeName = node.nodeName;

                        member = result[nodeName];

                        if (isArray(member)) {
                            // elements of same nodeName are stored as array
                            member.push(parsedNode);
                        } else if (member !== undefined) {
                            member = [member, parsedNode];
                        } else {
                            member = parsedNode;
                        }

                        result[nodeName] = member;
                    }
                }
                return result;
            },

            evaluate: function (value, expression) {
                var members = expression.split("."),
                member,
                result,
                length,
                intermediateResult,
                idx;

                while (member = members.shift()) {
                    value = value[member];

                    if (isArray(value)) {
                        result = [];
                        expression = members.join(".");

                        for (idx = 0, length = value.length; idx < length; idx++) {
                            intermediateResult = this.evaluate(value[idx], expression);

                            intermediateResult = isArray(intermediateResult) ? intermediateResult : [intermediateResult];

                            result.push.apply(result, intermediateResult);
                        }

                        return result;
                    }
                }

                return value;
            },

            parse: function (xml) {
                var documentElement,
                tree,
                result = {};

                documentElement = xml.documentElement || $.parseXML(xml).documentElement;

                tree = this.parseDOM(documentElement);

                result[documentElement.nodeName] = tree;

                return result;
            },

            xpathToMember: function (member, raw) {
                if (!member) {
                    return "";
                }

                member = member.replace(/^\//, "") // remove the first "/"
                           .replace(/\//g, "."); // replace all "/" with "."

                if (member.indexOf("@") >= 0) {
                    // replace @attribute with '["@attribute"]'
                    return member.replace(/\.?(@.*)/, raw ? '$1' : '["$1"]');
                }

                if (member.indexOf("text()") >= 0) {
                    // replace ".text()" with '["#text"]'
                    return member.replace(/(\.?text\(\))/, raw ? '#text' : '["#text"]');
                }

                return member;
            },
            getter: function (member) {
                return getter(this.xpathToMember(member), true);
            }
        });

        $.extend(true, kendo.data, {
            XmlDataReader: XmlDataReader,
            readers: {
                xml: XmlDataReader
            }
        });
    })(window.kendo.jQuery);





    /*jshint eqnull: true, loopfunc: true, evil: true */
    (function ($, undefined) {
        var extend = $.extend,
        proxy = $.proxy,
        isPlainObject = $.isPlainObject,
        isEmptyObject = $.isEmptyObject,
        isArray = $.isArray,
        grep = $.grep,
        ajax = $.ajax,
        map,
        each = $.each,
        noop = $.noop,
        kendo = window.kendo,
        isFunction = kendo.isFunction,
        Observable = kendo.Observable,
        Class = kendo.Class,
        STRING = "string",
        FUNCTION = "function",
        CREATE = "create",
        READ = "read",
        UPDATE = "update",
        DESTROY = "destroy",
        CHANGE = "change",
        SYNC = "sync",
        GET = "get",
        ERROR = "error",
        REQUESTSTART = "requestStart",
        PROGRESS = "progress",
        REQUESTEND = "requestEnd",
        crud = [CREATE, READ, UPDATE, DESTROY],
        identity = function (o) { return o; },
        getter = kendo.getter,
        stringify = kendo.stringify,
        math = Math,
        push = [].push,
        join = [].join,
        pop = [].pop,
        splice = [].splice,
        shift = [].shift,
        slice = [].slice,
        unshift = [].unshift,
        toString = {}.toString,
        stableSort = kendo.support.stableSort,
        dateRegExp = /^\/Date\((.*?)\)\/$/,
        newLineRegExp = /(\r+|\n+)/g,
        quoteRegExp = /(?=['\\])/g;

        var ObservableArray = Observable.extend({
            init: function (array, type) {
                var that = this;

                that.type = type || ObservableObject;

                Observable.fn.init.call(that);

                that.length = array.length;

                that.wrapAll(array, that);
            },

            at: function (index) {
                return this[index];
            },

            toJSON: function () {
                var idx, length = this.length, value, json = new Array(length);

                for (idx = 0; idx < length; idx++) {
                    value = this[idx];

                    if (value instanceof ObservableObject) {
                        value = value.toJSON();
                    }

                    json[idx] = value;
                }

                return json;
            },

            parent: noop,

            wrapAll: function (source, target) {
                var that = this,
                idx,
                length,
                parent = function () {
                    return that;
                };

                target = target || [];

                for (idx = 0, length = source.length; idx < length; idx++) {
                    target[idx] = that.wrap(source[idx], parent);
                }

                return target;
            },

            wrap: function (object, parent) {
                var that = this,
                observable;

                if (object !== null && toString.call(object) === "[object Object]") {
                    observable = object instanceof that.type || object instanceof Model;

                    if (!observable) {
                        object = object instanceof ObservableObject ? object.toJSON() : object;
                        object = new that.type(object);
                    }

                    object.parent = parent;

                    object.bind(CHANGE, function (e) {
                        that.trigger(CHANGE, {
                            field: e.field,
                            node: e.node,
                            index: e.index,
                            items: e.items || [this],
                            action: e.node ? (e.action || "itemchange") : "itemchange"
                        });
                    });
                }

                return object;
            },

            push: function () {
                var index = this.length,
                items = this.wrapAll(arguments),
                result;

                result = push.apply(this, items);

                this.trigger(CHANGE, {
                    action: "add",
                    index: index,
                    items: items
                });

                return result;
            },

            slice: slice,

            sort: [].sort,

            join: join,

            pop: function () {
                var length = this.length, result = pop.apply(this);

                if (length) {
                    this.trigger(CHANGE, {
                        action: "remove",
                        index: length - 1,
                        items: [result]
                    });
                }

                return result;
            },

            splice: function (index, howMany, item) {
                var items = this.wrapAll(slice.call(arguments, 2)),
                result, i, len;

                result = splice.apply(this, [index, howMany].concat(items));

                if (result.length) {
                    this.trigger(CHANGE, {
                        action: "remove",
                        index: index,
                        items: result
                    });

                    for (i = 0, len = result.length; i < len; i++) {
                        if (result[i].children) {
                            result[i].unbind(CHANGE);
                        }
                    }
                }

                if (item) {
                    this.trigger(CHANGE, {
                        action: "add",
                        index: index,
                        items: items
                    });
                }
                return result;
            },

            shift: function () {
                var length = this.length, result = shift.apply(this);

                if (length) {
                    this.trigger(CHANGE, {
                        action: "remove",
                        index: 0,
                        items: [result]
                    });
                }

                return result;
            },

            unshift: function () {
                var items = this.wrapAll(arguments),
                result;

                result = unshift.apply(this, items);

                this.trigger(CHANGE, {
                    action: "add",
                    index: 0,
                    items: items
                });

                return result;
            },

            indexOf: function (item) {
                var that = this,
                idx,
                length;

                for (idx = 0, length = that.length; idx < length; idx++) {
                    if (that[idx] === item) {
                        return idx;
                    }
                }
                return -1;
            },

            forEach: function (callback) {
                var idx = 0,
                length = this.length;

                for (; idx < length; idx++) {
                    callback(this[idx], idx, this);
                }
            },

            map: function (callback) {
                var idx = 0,
                result = [],
                length = this.length;

                for (; idx < length; idx++) {
                    result[idx] = callback(this[idx], idx, this);
                }

                return result;
            },

            filter: function (callback) {
                var idx = 0,
                result = [],
                item,
                length = this.length;

                for (; idx < length; idx++) {
                    item = this[idx];
                    if (callback(item, idx, this)) {
                        result[result.length] = item;
                    }
                }

                return result;
            },

            find: function (callback) {
                var idx = 0,
                item,
                length = this.length;

                for (; idx < length; idx++) {
                    item = this[idx];
                    if (callback(item, idx, this)) {
                        return item;
                    }
                }
            },

            every: function (callback) {
                var idx = 0,
                item,
                length = this.length;

                for (; idx < length; idx++) {
                    item = this[idx];
                    if (!callback(item, idx, this)) {
                        return false;
                    }
                }

                return true;
            },

            some: function (callback) {
                var idx = 0,
                item,
                length = this.length;

                for (; idx < length; idx++) {
                    item = this[idx];
                    if (callback(item, idx, this)) {
                        return true;
                    }
                }

                return false;
            },

            // non-standard collection methods
            remove: function (item) {
                var idx = this.indexOf(item);

                if (idx !== -1) {
                    this.splice(idx, 1);
                }
            },

            empty: function () {
                this.splice(0, this.length);
            }
        });

        var LazyObservableArray = ObservableArray.extend({
            init: function (data, type) {
                Observable.fn.init.call(this);

                this.type = type || ObservableObject;

                for (var idx = 0; idx < data.length; idx++) {
                    this[idx] = data[idx];
                }

                this.length = idx;
                this._parent = proxy(function () { return this; }, this);
            },
            at: function (index) {
                var item = this[index];

                if (!(item instanceof this.type)) {
                    item = this[index] = this.wrap(item, this._parent);
                } else {
                    item.parent = this._parent;
                }

                return item;
            }
        });

        function eventHandler(context, type, field, prefix) {
            return function (e) {
                var event = {}, key;

                for (key in e) {
                    event[key] = e[key];
                }

                if (prefix) {
                    event.field = field + "." + e.field;
                } else {
                    event.field = field;
                }

                if (type == CHANGE && context._notifyChange) {
                    context._notifyChange(event);
                }

                context.trigger(type, event);
            };
        }

        var ObservableObject = Observable.extend({
            init: function (value) {
                var that = this,
                member,
                field,
                parent = function () {
                    return that;
                };

                Observable.fn.init.call(this);

                for (field in value) {
                    member = value[field];

                    if (typeof member === "object" && member && !member.getTime && field.charAt(0) != "_") {
                        member = that.wrap(member, field, parent);
                    }

                    that[field] = member;
                }

                that.uid = kendo.guid();
            },

            shouldSerialize: function (field) {
                return this.hasOwnProperty(field) && field !== "_events" && typeof this[field] !== FUNCTION && field !== "uid";
            },

            forEach: function (f) {
                for (var i in this) {
                    if (this.shouldSerialize(i)) {
                        f(this[i], i);
                    }
                }
            },

            toJSON: function () {
                var result = {}, value, field;

                for (field in this) {
                    if (this.shouldSerialize(field)) {
                        value = this[field];

                        if (value instanceof ObservableObject || value instanceof ObservableArray) {
                            value = value.toJSON();
                        }

                        result[field] = value;
                    }
                }

                return result;
            },

            get: function (field) {
                var that = this, result;

                that.trigger(GET, { field: field });

                if (field === "this") {
                    result = that;
                } else {
                    result = kendo.getter(field, true)(that);
                }

                return result;
            },

            _set: function (field, value) {
                var that = this;
                var composite = field.indexOf(".") >= 0;

                if (composite) {
                    var paths = field.split("."),
                    path = "";

                    while (paths.length > 1) {
                        path += paths.shift();
                        var obj = kendo.getter(path, true)(that);
                        if (obj instanceof ObservableObject) {
                            obj.set(paths.join("."), value);
                            return composite;
                        }
                        path += ".";
                    }
                }

                kendo.setter(field)(that, value);

                return composite;
            },

            set: function (field, value) {
                var that = this,
                composite = field.indexOf(".") >= 0,
                current = kendo.getter(field, true)(that);

                if (current !== value) {

                    if (!that.trigger("set", { field: field, value: value })) {
                        if (!composite) {
                            value = that.wrap(value, field, function () { return that; });
                        }
                        if (!that._set(field, value) || field.indexOf("(") >= 0 || field.indexOf("[") >= 0) {
                            that.trigger(CHANGE, { field: field });
                        }
                    }
                }
            },

            parent: noop,

            wrap: function (object, field, parent) {
                var that = this,
                type = toString.call(object);

                if (object != null && (type === "[object Object]" || type === "[object Array]")) {
                    var isObservableArray = object instanceof ObservableArray;
                    var isDataSource = object instanceof DataSource;

                    if (type === "[object Object]" && !isDataSource && !isObservableArray) {
                        if (!(object instanceof ObservableObject)) {
                            object = new ObservableObject(object);
                        }

                        if (object.parent() != parent()) {
                            object.bind(GET, eventHandler(that, GET, field, true));
                            object.bind(CHANGE, eventHandler(that, CHANGE, field, true));
                        }
                    } else if (type === "[object Array]" || isObservableArray || isDataSource) {
                        if (!isObservableArray && !isDataSource) {
                            object = new ObservableArray(object);
                        }

                        if (object.parent() != parent()) {
                            object.bind(CHANGE, eventHandler(that, CHANGE, field, false));
                        }
                    }

                    object.parent = parent;
                }

                return object;
            }
        });

        function equal(x, y) {
            if (x === y) {
                return true;
            }

            var xtype = $.type(x), ytype = $.type(y), field;

            if (xtype !== ytype) {
                return false;
            }

            if (xtype === "date") {
                return x.getTime() === y.getTime();
            }

            if (xtype !== "object" && xtype !== "array") {
                return false;
            }

            for (field in x) {
                if (!equal(x[field], y[field])) {
                    return false;
                }
            }

            return true;
        }

        var parsers = {
            "number": function (value) {
                return kendo.parseFloat(value);
            },

            "date": function (value) {
                return kendo.parseDate(value);
            },

            "boolean": function (value) {
                if (typeof value === STRING) {
                    return value.toLowerCase() === "true";
                }
                return value != null ? !!value : value;
            },

            "string": function (value) {
                return value != null ? (value + "") : value;
            },

            "default": function (value) {
                return value;
            }
        };

        var defaultValues = {
            "string": "",
            "number": 0,
            "date": new Date(),
            "boolean": false,
            "default": ""
        };

        function getFieldByName(obj, name) {
            var field,
            fieldName;

            for (fieldName in obj) {
                field = obj[fieldName];
                if (isPlainObject(field) && field.field && field.field === name) {
                    return field;
                } else if (field === name) {
                    return field;
                }
            }
            return null;
        }

        var Model = ObservableObject.extend({
            init: function (data) {
                var that = this;

                if (!data || $.isEmptyObject(data)) {
                    data = $.extend({}, that.defaults, data);

                    if (that._initializers) {
                        for (var idx = 0; idx < that._initializers.length; idx++) {
                            var name = that._initializers[idx];
                            data[name] = that.defaults[name]();
                        }
                    }
                }

                ObservableObject.fn.init.call(that, data);

                that.dirty = false;

                if (that.idField) {
                    that.id = that.get(that.idField);

                    if (that.id === undefined) {
                        that.id = that._defaultId;
                    }
                }
            },

            shouldSerialize: function (field) {
                return ObservableObject.fn.shouldSerialize.call(this, field) && field !== "uid" && !(this.idField !== "id" && field === "id") && field !== "dirty" && field !== "_accessors";
            },

            _parse: function (field, value) {
                var that = this,
                fieldName = field,
                fields = (that.fields || {}),
                parse;

                field = fields[field];
                if (!field) {
                    field = getFieldByName(fields, fieldName);
                }
                if (field) {
                    parse = field.parse;
                    if (!parse && field.type) {
                        parse = parsers[field.type.toLowerCase()];
                    }
                }

                return parse ? parse(value) : value;
            },

            _notifyChange: function (e) {
                var action = e.action;

                if (action == "add" || action == "remove") {
                    this.dirty = true;
                }
            },

            editable: function (field) {
                field = (this.fields || {})[field];
                return field ? field.editable !== false : true;
            },

            set: function (field, value, initiator) {
                var that = this;

                if (that.editable(field)) {
                    value = that._parse(field, value);

                    if (!equal(value, that.get(field))) {
                        that.dirty = true;
                        ObservableObject.fn.set.call(that, field, value, initiator);
                    }
                }
            },

            accept: function (data) {
                var that = this,
                parent = function () { return that; },
                field;

                for (field in data) {
                    var value = data[field];

                    if (field.charAt(0) != "_") {
                        value = that.wrap(data[field], field, parent);
                    }

                    that._set(field, value);
                }

                if (that.idField) {
                    that.id = that.get(that.idField);
                }

                that.dirty = false;
            },

            isNew: function () {
                return this.id === this._defaultId;
            }
        });

        Model.define = function (base, options) {
            if (options === undefined) {
                options = base;
                base = Model;
            }

            var model,
            proto = extend({ defaults: {} }, options),
            name,
            field,
            type,
            value,
            idx,
            length,
            fields = {},
            originalName,
            id = proto.id,
            functionFields = [];

            if (id) {
                proto.idField = id;
            }

            if (proto.id) {
                delete proto.id;
            }

            if (id) {
                proto.defaults[id] = proto._defaultId = "";
            }

            if (toString.call(proto.fields) === "[object Array]") {
                for (idx = 0, length = proto.fields.length; idx < length; idx++) {
                    field = proto.fields[idx];
                    if (typeof field === STRING) {
                        fields[field] = {};
                    } else if (field.field) {
                        fields[field.field] = field;
                    }
                }
                proto.fields = fields;
            }

            for (name in proto.fields) {
                field = proto.fields[name];
                type = field.type || "default";
                value = null;
                originalName = name;

                name = typeof (field.field) === STRING ? field.field : name;

                if (!field.nullable) {
                    value = proto.defaults[originalName !== name ? originalName : name] = field.defaultValue !== undefined ? field.defaultValue : defaultValues[type.toLowerCase()];

                    if (typeof value === "function") {
                        functionFields.push(name);
                    }
                }

                if (options.id === name) {
                    proto._defaultId = value;
                }

                proto.defaults[originalName !== name ? originalName : name] = value;

                field.parse = field.parse || parsers[type];
            }

            if (functionFields.length > 0) {
                proto._initializers = functionFields;
            }

            model = base.extend(proto);
            model.define = function (options) {
                return Model.define(model, options);
            };

            if (proto.fields) {
                model.fields = proto.fields;
                model.idField = proto.idField;
            }

            return model;
        };

        var Comparer = {
            selector: function (field) {
                return isFunction(field) ? field : getter(field);
            },

            compare: function (field) {
                var selector = this.selector(field);
                return function (a, b) {
                    a = selector(a);
                    b = selector(b);

                    if (a == null && b == null) {
                        return 0;
                    }

                    if (a == null) {
                        return -1;
                    }

                    if (b == null) {
                        return 1;
                    }

                    if (a.localeCompare) {
                        return a.localeCompare(b);
                    }

                    return a > b ? 1 : (a < b ? -1 : 0);
                };
            },

            create: function (sort) {
                var compare = sort.compare || this.compare(sort.field);

                if (sort.dir == "desc") {
                    return function (a, b) {
                        return compare(b, a, true);
                    };
                }

                return compare;
            },

            combine: function (comparers) {
                return function (a, b) {
                    var result = comparers[0](a, b),
                    idx,
                    length;

                    for (idx = 1, length = comparers.length; idx < length; idx++) {
                        result = result || comparers[idx](a, b);
                    }

                    return result;
                };
            }
        };

        var StableComparer = extend({}, Comparer, {
            asc: function (field) {
                var selector = this.selector(field);
                return function (a, b) {
                    var valueA = selector(a);
                    var valueB = selector(b);

                    if (valueA && valueA.getTime && valueB && valueB.getTime) {
                        valueA = valueA.getTime();
                        valueB = valueB.getTime();
                    }

                    if (valueA === valueB) {
                        return a.__position - b.__position;
                    }

                    if (valueA == null) {
                        return -1;
                    }

                    if (valueB == null) {
                        return 1;
                    }

                    if (valueA.localeCompare) {
                        return valueA.localeCompare(valueB);
                    }

                    return valueA > valueB ? 1 : -1;
                };
            },

            desc: function (field) {
                var selector = this.selector(field);
                return function (a, b) {
                    var valueA = selector(a);
                    var valueB = selector(b);

                    if (valueA && valueA.getTime && valueB && valueB.getTime) {
                        valueA = valueA.getTime();
                        valueB = valueB.getTime();
                    }

                    if (valueA === valueB) {
                        return a.__position - b.__position;
                    }

                    if (valueA == null) {
                        return 1;
                    }

                    if (valueB == null) {
                        return -1;
                    }

                    if (valueB.localeCompare) {
                        return valueB.localeCompare(valueA);
                    }

                    return valueA < valueB ? 1 : -1;
                };
            },
            create: function (sort) {
                return this[sort.dir](sort.field);
            }
        });

        map = function (array, callback) {
            var idx, length = array.length, result = new Array(length);

            for (idx = 0; idx < length; idx++) {
                result[idx] = callback(array[idx], idx, array);
            }

            return result;
        };

        var operators = (function () {

            function quote(value) {
                return value.replace(quoteRegExp, "\\").replace(newLineRegExp, "");
            }

            function operator(op, a, b, ignore) {
                var date;

                if (b != null) {
                    if (typeof b === STRING) {
                        b = quote(b);
                        date = dateRegExp.exec(b);
                        if (date) {
                            b = new Date(+date[1]);
                        } else if (ignore) {
                            b = "'" + b.toLowerCase() + "'";
                            a = "(" + a + " || '').toLowerCase()";
                        } else {
                            b = "'" + b + "'";
                        }
                    }

                    if (b.getTime) {
                        //b looks like a Date
                        a = "(" + a + "?" + a + ".getTime():" + a + ")";
                        b = b.getTime();
                    }
                }

                return a + " " + op + " " + b;
            }

            return {
                eq: function (a, b, ignore) {
                    return operator("==", a, b, ignore);
                },
                neq: function (a, b, ignore) {
                    return operator("!=", a, b, ignore);
                },
                gt: function (a, b, ignore) {
                    return operator(">", a, b, ignore);
                },
                gte: function (a, b, ignore) {
                    return operator(">=", a, b, ignore);
                },
                lt: function (a, b, ignore) {
                    return operator("<", a, b, ignore);
                },
                lte: function (a, b, ignore) {
                    return operator("<=", a, b, ignore);
                },
                startswith: function (a, b, ignore) {
                    if (ignore) {
                        a = "(" + a + " || '').toLowerCase()";
                        if (b) {
                            b = b.toLowerCase();
                        }
                    }

                    if (b) {
                        b = quote(b);
                    }

                    return a + ".lastIndexOf('" + b + "', 0) == 0";
                },
                endswith: function (a, b, ignore) {
                    if (ignore) {
                        a = "(" + a + " || '').toLowerCase()";
                        if (b) {
                            b = b.toLowerCase();
                        }
                    }

                    if (b) {
                        b = quote(b);
                    }

                    return a + ".indexOf('" + b + "', " + a + ".length - " + (b || "").length + ") >= 0";
                },
                contains: function (a, b, ignore) {
                    if (ignore) {
                        a = "(" + a + " || '').toLowerCase()";
                        if (b) {
                            b = b.toLowerCase();
                        }
                    }

                    if (b) {
                        b = quote(b);
                    }

                    return a + ".indexOf('" + b + "') >= 0";
                },
                doesnotcontain: function (a, b, ignore) {
                    if (ignore) {
                        a = "(" + a + " || '').toLowerCase()";
                        if (b) {
                            b = b.toLowerCase();
                        }
                    }

                    if (b) {
                        b = quote(b);
                    }

                    return a + ".indexOf('" + b + "') == -1";
                }
            };
        })();

        function Query(data) {
            this.data = data || [];
        }

        Query.filterExpr = function (expression) {
            var expressions = [],
            logic = { and: " && ", or: " || " },
            idx,
            length,
            filter,
            expr,
            fieldFunctions = [],
            operatorFunctions = [],
            field,
            operator,
            filters = expression.filters;

            for (idx = 0, length = filters.length; idx < length; idx++) {
                filter = filters[idx];
                field = filter.field;
                operator = filter.operator;

                if (filter.filters) {
                    expr = Query.filterExpr(filter);
                    //Nested function fields or operators - update their index e.g. __o[0] -> __o[1]
                    filter = expr.expression
                .replace(/__o\[(\d+)\]/g, function (match, index) {
                    index = +index;
                    return "__o[" + (operatorFunctions.length + index) + "]";
                })
                .replace(/__f\[(\d+)\]/g, function (match, index) {
                    index = +index;
                    return "__f[" + (fieldFunctions.length + index) + "]";
                });

                    operatorFunctions.push.apply(operatorFunctions, expr.operators);
                    fieldFunctions.push.apply(fieldFunctions, expr.fields);
                } else {
                    if (typeof field === FUNCTION) {
                        expr = "__f[" + fieldFunctions.length + "](d)";
                        fieldFunctions.push(field);
                    } else {
                        expr = kendo.expr(field);
                    }

                    if (typeof operator === FUNCTION) {
                        filter = "__o[" + operatorFunctions.length + "](" + expr + ", " + filter.value + ")";
                        operatorFunctions.push(operator);
                    } else {
                        filter = operators[(operator || "eq").toLowerCase()](expr, filter.value, filter.ignoreCase !== undefined ? filter.ignoreCase : true);
                    }
                }

                expressions.push(filter);
            }

            return { expression: "(" + expressions.join(logic[expression.logic]) + ")", fields: fieldFunctions, operators: operatorFunctions };
        };

        function normalizeSort(field, dir) {
            if (field) {
                var descriptor = typeof field === STRING ? { field: field, dir: dir} : field,
            descriptors = isArray(descriptor) ? descriptor : (descriptor !== undefined ? [descriptor] : []);

                return grep(descriptors, function (d) { return !!d.dir; });
            }
        }

        var operatorMap = {
            "==": "eq",
            equals: "eq",
            isequalto: "eq",
            equalto: "eq",
            equal: "eq",
            "!=": "neq",
            ne: "neq",
            notequals: "neq",
            isnotequalto: "neq",
            notequalto: "neq",
            notequal: "neq",
            "<": "lt",
            islessthan: "lt",
            lessthan: "lt",
            less: "lt",
            "<=": "lte",
            le: "lte",
            islessthanorequalto: "lte",
            lessthanequal: "lte",
            ">": "gt",
            isgreaterthan: "gt",
            greaterthan: "gt",
            greater: "gt",
            ">=": "gte",
            isgreaterthanorequalto: "gte",
            greaterthanequal: "gte",
            ge: "gte",
            notsubstringof: "doesnotcontain"
        };

        function normalizeOperator(expression) {
            var idx,
        length,
        filter,
        operator,
        filters = expression.filters;

            if (filters) {
                for (idx = 0, length = filters.length; idx < length; idx++) {
                    filter = filters[idx];
                    operator = filter.operator;

                    if (operator && typeof operator === STRING) {
                        filter.operator = operatorMap[operator.toLowerCase()] || operator;
                    }

                    normalizeOperator(filter);
                }
            }
        }

        function normalizeFilter(expression) {
            if (expression && !isEmptyObject(expression)) {
                if (isArray(expression) || !expression.filters) {
                    expression = {
                        logic: "and",
                        filters: isArray(expression) ? expression : [expression]
                    };
                }

                normalizeOperator(expression);

                return expression;
            }
        }

        Query.normalizeFilter = normalizeFilter;

        function normalizeAggregate(expressions) {
            return isArray(expressions) ? expressions : [expressions];
        }

        function normalizeGroup(field, dir) {
            var descriptor = typeof field === STRING ? { field: field, dir: dir} : field,
        descriptors = isArray(descriptor) ? descriptor : (descriptor !== undefined ? [descriptor] : []);

            return map(descriptors, function (d) { return { field: d.field, dir: d.dir || "asc", aggregates: d.aggregates }; });
        }

        Query.prototype = {
            toArray: function () {
                return this.data;
            },
            range: function (index, count) {
                return new Query(this.data.slice(index, index + count));
            },
            skip: function (count) {
                return new Query(this.data.slice(count));
            },
            take: function (count) {
                return new Query(this.data.slice(0, count));
            },
            select: function (selector) {
                return new Query(map(this.data, selector));
            },
            order: function (selector, dir) {
                var sort = { dir: dir };

                if (selector) {
                    if (selector.compare) {
                        sort.compare = selector.compare;
                    } else {
                        sort.field = selector;
                    }
                }

                return new Query(this.data.slice(0).sort(Comparer.create(sort)));
            },
            orderBy: function (selector) {
                return this.order(selector, "asc");
            },
            orderByDescending: function (selector) {
                return this.order(selector, "desc");
            },
            sort: function (field, dir, comparer) {
                var idx,
            length,
            descriptors = normalizeSort(field, dir),
            comparers = [];

                comparer = comparer || Comparer;

                if (descriptors.length) {
                    for (idx = 0, length = descriptors.length; idx < length; idx++) {
                        comparers.push(comparer.create(descriptors[idx]));
                    }

                    return this.orderBy({ compare: comparer.combine(comparers) });
                }

                return this;
            },

            filter: function (expressions) {
                var idx,
            current,
            length,
            compiled,
            predicate,
            data = this.data,
            fields,
            operators,
            result = [],
            filter;

                expressions = normalizeFilter(expressions);

                if (!expressions || expressions.filters.length === 0) {
                    return this;
                }

                compiled = Query.filterExpr(expressions);
                fields = compiled.fields;
                operators = compiled.operators;

                predicate = filter = new Function("d, __f, __o", "return " + compiled.expression);

                if (fields.length || operators.length) {
                    filter = function (d) {
                        return predicate(d, fields, operators);
                    };
                }


                for (idx = 0, length = data.length; idx < length; idx++) {
                    current = data[idx];

                    if (filter(current)) {
                        result.push(current);
                    }
                }

                return new Query(result);
            },

            group: function (descriptors, allData) {
                descriptors = normalizeGroup(descriptors || []);
                allData = allData || this.data;

                var that = this,
            result = new Query(that.data),
            descriptor;

                if (descriptors.length > 0) {
                    descriptor = descriptors[0];
                    result = result.groupBy(descriptor).select(function (group) {
                        var data = new Query(allData).filter([{ field: group.field, operator: "eq", value: group.value, ignoreCase: false}]);
                        return {
                            field: group.field,
                            value: group.value,
                            items: descriptors.length > 1 ? new Query(group.items).group(descriptors.slice(1), data.toArray()).toArray() : group.items,
                            hasSubgroups: descriptors.length > 1,
                            aggregates: data.aggregate(descriptor.aggregates)
                        };
                    });
                }
                return result;
            },

            groupBy: function (descriptor) {
                if (isEmptyObject(descriptor) || !this.data.length) {
                    return new Query([]);
                }

                var field = descriptor.field,
                sorted = this._sortForGrouping(field, descriptor.dir || "asc"),
                accessor = kendo.accessor(field),
                item,
                groupValue = accessor.get(sorted[0], field),
                group = {
                    field: field,
                    value: groupValue,
                    items: []
                },
                currentValue,
                idx,
                len,
                result = [group];

                for (idx = 0, len = sorted.length; idx < len; idx++) {
                    item = sorted[idx];
                    currentValue = accessor.get(item, field);
                    if (!groupValueComparer(groupValue, currentValue)) {
                        groupValue = currentValue;
                        group = {
                            field: field,
                            value: groupValue,
                            items: []
                        };
                        result.push(group);
                    }
                    group.items.push(item);
                }
                return new Query(result);
            },

            _sortForGrouping: function (field, dir) {
                var idx, length,
                data = this.data;

                if (!stableSort) {
                    for (idx = 0, length = data.length; idx < length; idx++) {
                        data[idx].__position = idx;
                    }

                    data = new Query(data).sort(field, dir, StableComparer).toArray();

                    for (idx = 0, length = data.length; idx < length; idx++) {
                        delete data[idx].__position;
                    }
                    return data;
                }
                return this.sort(field, dir).toArray();
            },

            aggregate: function (aggregates) {
                var idx,
                len,
                result = {},
                state = {};

                if (aggregates && aggregates.length) {
                    for (idx = 0, len = this.data.length; idx < len; idx++) {
                        calculateAggregate(result, aggregates, this.data[idx], idx, len, state);
                    }
                }
                return result;
            }
        };

        function groupValueComparer(a, b) {
            if (a && a.getTime && b && b.getTime) {
                return a.getTime() === b.getTime();
            }
            return a === b;
        }

        function calculateAggregate(accumulator, aggregates, item, index, length, state) {
            aggregates = aggregates || [];
            var idx,
            aggr,
            functionName,
            len = aggregates.length;

            for (idx = 0; idx < len; idx++) {
                aggr = aggregates[idx];
                functionName = aggr.aggregate;
                var field = aggr.field;
                accumulator[field] = accumulator[field] || {};
                state[field] = state[field] || {};
                state[field][functionName] = state[field][functionName] || {};
                accumulator[field][functionName] = functions[functionName.toLowerCase()](accumulator[field][functionName], item, kendo.accessor(field), index, length, state[field][functionName]);
            }
        }

        var functions = {
            sum: function (accumulator, item, accessor) {
                var value = accessor.get(item);

                if (!isNumber(accumulator)) {
                    accumulator = value;
                } else if (isNumber(value)) {
                    accumulator += value;
                }

                return accumulator;
            },
            count: function (accumulator) {
                return (accumulator || 0) + 1;
            },
            average: function (accumulator, item, accessor, index, length, state) {
                var value = accessor.get(item);

                if (state.count === undefined) {
                    state.count = 0;
                }

                if (!isNumber(accumulator)) {
                    accumulator = value;
                } else if (isNumber(value)) {
                    accumulator += value;
                }

                if (isNumber(value)) {
                    state.count++;
                }

                if (index == length - 1 && isNumber(accumulator)) {
                    accumulator = accumulator / state.count;
                }
                return accumulator;
            },
            max: function (accumulator, item, accessor) {
                var value = accessor.get(item);

                if (!isNumber(accumulator) && !isDate(accumulator)) {
                    accumulator = value;
                }

                if (accumulator < value && (isNumber(value) || isDate(value))) {
                    accumulator = value;
                }
                return accumulator;
            },
            min: function (accumulator, item, accessor) {
                var value = accessor.get(item);

                if (!isNumber(accumulator) && !isDate(accumulator)) {
                    accumulator = value;
                }

                if (accumulator > value && (isNumber(value) || isDate(value))) {
                    accumulator = value;
                }
                return accumulator;
            }
        };

        function isNumber(val) {
            return typeof val === "number" && !isNaN(val);
        }

        function isDate(val) {
            return val && val.getTime;
        }

        function toJSON(array) {
            var idx, length = array.length, result = new Array(length);

            for (idx = 0; idx < length; idx++) {
                result[idx] = array[idx].toJSON();
            }

            return result;
        }

        Query.process = function (data, options) {
            options = options || {};

            var query = new Query(data),
            group = options.group,
            sort = normalizeGroup(group || []).concat(normalizeSort(options.sort || [])),
            total,
            filterCallback = options.filterCallback,
            filter = options.filter,
            skip = options.skip,
            take = options.take;

            if (filter) {
                query = query.filter(filter);

                if (filterCallback) {
                    query = filterCallback(query);
                }

                total = query.toArray().length;
            }

            if (sort) {
                query = query.sort(sort);

                if (group) {
                    data = query.toArray();
                }
            }

            if (skip !== undefined && take !== undefined) {
                query = query.range(skip, take);
            }

            if (group) {
                query = query.group(group, data);
            }

            return {
                total: total,
                data: query.toArray()
            };
        };

        var LocalTransport = Class.extend({
            init: function (options) {
                this.data = options.data;
            },

            read: function (options) {
                options.success(this.data);
            },
            update: function (options) {
                options.success(options.data);
            },
            create: function (options) {
                options.success(options.data);
            },
            destroy: function (options) {
                options.success(options.data);
            }
        });

        var RemoteTransport = Class.extend({
            init: function (options) {
                var that = this, parameterMap;

                options = that.options = extend({}, that.options, options);

                each(crud, function (index, type) {
                    if (typeof options[type] === STRING) {
                        options[type] = {
                            url: options[type]
                        };
                    }
                });

                that.cache = options.cache ? Cache.create(options.cache) : {
                    find: noop,
                    add: noop
                };

                parameterMap = options.parameterMap;

                if (isFunction(options.push)) {
                    that.push = options.push;
                }

                if (!that.push) {
                    that.push = identity;
                }

                that.parameterMap = isFunction(parameterMap) ? parameterMap : function (options) {
                    var result = {};

                    each(options, function (option, value) {
                        if (option in parameterMap) {
                            option = parameterMap[option];
                            if (isPlainObject(option)) {
                                value = option.value(value);
                                option = option.key;
                            }
                        }

                        result[option] = value;
                    });

                    return result;
                };
            },

            options: {
                parameterMap: identity
            },

            create: function (options) {
                return ajax(this.setup(options, CREATE));
            },

            read: function (options) {
                var that = this,
                success,
                error,
                result,
                cache = that.cache;

                options = that.setup(options, READ);

                success = options.success || noop;
                error = options.error || noop;

                result = cache.find(options.data);

                if (result !== undefined) {
                    success(result);
                } else {
                    options.success = function (result) {
                        cache.add(options.data, result);

                        success(result);
                    };

                    $.ajax(options);
                }
            },

            update: function (options) {
                return ajax(this.setup(options, UPDATE));
            },

            destroy: function (options) {
                return ajax(this.setup(options, DESTROY));
            },

            setup: function (options, type) {
                options = options || {};

                var that = this,
                parameters,
                operation = that.options[type],
                data = isFunction(operation.data) ? operation.data(options.data) : operation.data;

                options = extend(true, {}, operation, options);
                parameters = extend(true, {}, data, options.data);

                options.data = that.parameterMap(parameters, type);

                if (isFunction(options.url)) {
                    options.url = options.url(parameters);
                }

                return options;
            }
        });

        var Cache = Class.extend({
            init: function () {
                this._store = {};
            },
            add: function (key, data) {
                if (key !== undefined) {
                    this._store[stringify(key)] = data;
                }
            },
            find: function (key) {
                return this._store[stringify(key)];
            },
            clear: function () {
                this._store = {};
            },
            remove: function (key) {
                delete this._store[stringify(key)];
            }
        });

        Cache.create = function (options) {
            var store = {
                "inmemory": function () { return new Cache(); }
            };

            if (isPlainObject(options) && isFunction(options.find)) {
                return options;
            }

            if (options === true) {
                return new Cache();
            }

            return store[options]();
        };

        function serializeRecords(data, getters, modelInstance, originalFieldNames, fieldNames) {
            var record,
            getter,
            originalName,
            idx,
            length;

            for (idx = 0, length = data.length; idx < length; idx++) {
                record = data[idx];
                for (getter in getters) {
                    originalName = fieldNames[getter];

                    if (originalName && originalName !== getter) {
                        record[originalName] = getters[getter](record);
                        delete record[getter];
                    }
                }
            }
        }

        function convertRecords(data, getters, modelInstance, originalFieldNames, fieldNames) {
            var record,
            getter,
            originalName,
            idx,
            length;

            for (idx = 0, length = data.length; idx < length; idx++) {
                record = data[idx];
                for (getter in getters) {
                    record[getter] = modelInstance._parse(getter, getters[getter](record));

                    originalName = fieldNames[getter];
                    if (originalName && originalName !== getter) {
                        delete record[originalName];
                    }
                }
            }
        }

        function convertGroup(data, getters, modelInstance, originalFieldNames, fieldNames) {
            var record,
            idx,
            fieldName,
            length;

            for (idx = 0, length = data.length; idx < length; idx++) {
                record = data[idx];

                fieldName = originalFieldNames[record.field];
                if (fieldName && fieldName != record.field) {
                    record.field = fieldName;
                }

                record.value = modelInstance._parse(record.field, record.value);

                if (record.hasSubgroups) {
                    convertGroup(record.items, getters, modelInstance, originalFieldNames, fieldNames);
                } else {
                    convertRecords(record.items, getters, modelInstance, originalFieldNames, fieldNames);
                }
            }
        }

        function wrapDataAccess(originalFunction, model, converter, getters, originalFieldNames, fieldNames) {
            return function (data) {
                data = originalFunction(data);

                if (data && !isEmptyObject(getters)) {
                    if (toString.call(data) !== "[object Array]" && !(data instanceof ObservableArray)) {
                        data = [data];
                    }

                    converter(data, getters, new model(), originalFieldNames, fieldNames);
                }

                return data || [];
            };
        }

        var DataReader = Class.extend({
            init: function (schema) {
                var that = this, member, get, model, base;

                schema = schema || {};

                for (member in schema) {
                    get = schema[member];

                    that[member] = typeof get === STRING ? getter(get) : get;
                }

                base = schema.modelBase || Model;

                if (isPlainObject(that.model)) {
                    that.model = model = base.define(that.model);
                }

                var dataFunction = proxy(that.data, that);

                that._dataAccessFunction = dataFunction;

                if (that.model) {
                    var groupsFunction = proxy(that.groups, that),
                    serializeFunction = proxy(that.serialize, that),
                    originalFieldNames = {},
                    getters = {},
                    serializeGetters = {},
                    fieldNames = {},
                    shouldSerialize = false,
                    fieldName;

                    model = that.model;

                    if (model.fields) {
                        each(model.fields, function (field, value) {
                            var fromName;

                            fieldName = field;

                            if (isPlainObject(value) && value.field) {
                                fieldName = value.field;
                            } else if (typeof value === STRING) {
                                fieldName = value;
                            }

                            if (isPlainObject(value) && value.from) {
                                fromName = value.from;
                            }

                            shouldSerialize = shouldSerialize || (fromName && fromName !== field) || fieldName !== field;

                            getters[field] = getter(fromName || fieldName);
                            serializeGetters[field] = getter(field);
                            originalFieldNames[fromName || fieldName] = field;
                            fieldNames[field] = fromName || fieldName;
                        });

                        if (!schema.serialize && shouldSerialize) {
                            that.serialize = wrapDataAccess(serializeFunction, model, serializeRecords, serializeGetters, originalFieldNames, fieldNames);
                        }
                    }

                    that._dataAccessFunction = dataFunction;
                    that.data = wrapDataAccess(dataFunction, model, convertRecords, getters, originalFieldNames, fieldNames);
                    that.groups = wrapDataAccess(groupsFunction, model, convertGroup, getters, originalFieldNames, fieldNames);
                }
            },
            errors: function (data) {
                return data ? data.errors : null;
            },
            parse: identity,
            data: identity,
            total: function (data) {
                return data.length;
            },
            groups: identity,
            aggregates: function () {
                return {};
            },
            serialize: function (data) {
                return data;
            }
        });

        function mergeGroups(target, dest, skip, take) {
            var group,
            idx = 0,
            items;

            while (dest.length && take) {
                group = dest[idx];
                items = group.items;

                var length = items.length;

                if (target && target.field === group.field && target.value === group.value) {
                    if (target.hasSubgroups && target.items.length) {
                        mergeGroups(target.items[target.items.length - 1], group.items, skip, take);
                    } else {
                        items = items.slice(skip, skip + take);
                        target.items = target.items.concat(items);
                    }
                    dest.splice(idx--, 1);
                } else if (group.hasSubgroups && items.length) {
                    mergeGroups(group, items, skip, take);
                } else {
                    items = items.slice(skip, skip + take);
                    group.items = items;

                    if (!group.items.length) {
                        dest.splice(idx--, 1);
                    }
                }

                if (items.length === 0) {
                    skip -= length;
                } else {
                    skip = 0;
                    take -= items.length;
                }

                if (++idx >= dest.length) {
                    break;
                }
            }

            if (idx < dest.length) {
                dest.splice(idx, dest.length - idx);
            }
        }

        function flattenGroups(data) {
            var idx,
            result = [],
            length,
            items,
            itemIndex;

            for (idx = 0, length = data.length; idx < length; idx++) {
                var group = data.at(idx);
                if (group.hasSubgroups) {
                    result = result.concat(flattenGroups(group.items));
                } else {
                    items = group.items;
                    for (itemIndex = 0; itemIndex < items.length; itemIndex++) {
                        result.push(items.at(itemIndex));
                    }
                }
            }
            return result;
        }

        function wrapGroupItems(data, model) {
            var idx, length, group, items;
            if (model) {
                for (idx = 0, length = data.length; idx < length; idx++) {
                    group = data.at(idx);

                    if (group.hasSubgroups) {
                        wrapGroupItems(group.items, model);
                    } else {
                        group.items = new LazyObservableArray(group.items, model);
                    }
                }
            }
        }

        function eachGroupItems(data, func) {
            for (var idx = 0, length = data.length; idx < length; idx++) {
                if (data[idx].hasSubgroups) {
                    if (eachGroupItems(data[idx].items, func)) {
                        return true;
                    }
                } else if (func(data[idx].items, data[idx])) {
                    return true;
                }
            }
        }

        function replaceInRanges(ranges, data, item, observable) {
            for (var idx = 0; idx < ranges.length; idx++) {
                if (ranges[idx].data === data) {
                    break;
                }
                if (replaceInRange(ranges[idx].data, item, observable)) {
                    break;
                }
            }
        }

        function replaceInRange(items, item, observable) {
            for (var idx = 0, length = items.length; idx < length; idx++) {
                if (items[idx] && items[idx].hasSubgroups) {
                    return replaceInRange(items[idx].items, item, observable);
                } else if (items[idx] === item || items[idx] === observable) {
                    items[idx] = observable;
                    return true;
                }
            }
        }

        function replaceWithObservable(view, data, ranges, type) {
            for (var viewIndex = 0, length = view.length; viewIndex < length; viewIndex++) {
                var item = view[viewIndex];

                if (!item || item instanceof type) {
                    continue;
                }

                if (item.hasSubgroups !== undefined) {
                    replaceWithObservable(item.items, data, ranges, type);
                } else {
                    for (var idx = 0; idx < data.length; idx++) {
                        if (data[idx] === item) {
                            view[viewIndex] = data.at(idx);
                            replaceInRanges(ranges, data, item, view[viewIndex]);
                            break;
                        }
                    }
                }
            }
        }

        function removeModel(data, model) {
            var idx, length;

            for (idx = 0, length = data.length; idx < length; idx++) {
                var dataItem = data.at(idx);
                if (dataItem.uid == model.uid) {
                    data.splice(idx, 1);
                    return dataItem;
                }
            }
        }

        function wrapInEmptyGroup(groups, model) {
            var parent,
            group,
            idx,
            length;

            for (idx = groups.length - 1, length = 0; idx >= length; idx--) {
                group = groups[idx];
                parent = {
                    value: model.get(group.field),
                    field: group.field,
                    items: parent ? [parent] : [model],
                    hasSubgroups: !!parent,
                    aggregates: {}
                };
            }

            return parent;
        }

        function indexOfPristineModel(data, model) {
            if (model) {
                return indexOf(data, function (item) {
                    if (item.uid) {
                        return item.uid == model.uid;
                    }

                    return item[model.idField] === model.id;
                });
            }
            return -1;
        }

        function indexOfModel(data, model) {
            if (model) {
                return indexOf(data, function (item) {
                    return item.uid == model.uid;
                });
            }
            return -1;
        }

        function indexOf(data, comparer) {
            var idx, length;

            for (idx = 0, length = data.length; idx < length; idx++) {
                if (comparer(data[idx])) {
                    return idx;
                }
            }

            return -1;
        }

        function fieldNameFromModel(fields, name) {
            if (fields && !isEmptyObject(fields)) {
                var descriptor = fields[name];
                var fieldName;
                if (isPlainObject(descriptor)) {
                    fieldName = descriptor.from || descriptor.field || name;
                } else {
                    fieldName = fields[name] || name;
                }

                if (isFunction(fieldName)) {
                    return name;
                }

                return fieldName;
            }
            return name;
        }

        function convertFilterDescriptorsField(descriptor, model) {
            var idx,
            length,
            target = {};

            for (var field in descriptor) {
                if (field !== "filters") {
                    target[field] = descriptor[field];
                }
            }

            if (descriptor.filters) {
                target.filters = [];
                for (idx = 0, length = descriptor.filters.length; idx < length; idx++) {
                    target.filters[idx] = convertFilterDescriptorsField(descriptor.filters[idx], model);
                }
            } else {
                target.field = fieldNameFromModel(model.fields, target.field);
            }
            return target;
        }

        function convertDescriptorsField(descriptors, model) {
            var idx,
            length,
            result = [],
            target,
            descriptor;

            for (idx = 0, length = descriptors.length; idx < length; idx++) {
                target = {};

                descriptor = descriptors[idx];

                for (var field in descriptor) {
                    target[field] = descriptor[field];
                }

                target.field = fieldNameFromModel(model.fields, target.field);

                if (target.aggregates && isArray(target.aggregates)) {
                    target.aggregates = convertDescriptorsField(target.aggregates, model);
                }
                result.push(target);
            }
            return result;
        }

        var DataSource = Observable.extend({
            init: function (options) {
                var that = this, model, data;

                if (options) {
                    data = options.data;
                }

                options = that.options = extend({}, that.options, options);

                that._map = {};
                that._prefetch = {};
                that._data = [];
                that._pristineData = [];
                that._ranges = [];
                that._view = [];
                that._pristineTotal = 0;
                that._destroyed = [];
                that._pageSize = options.pageSize;
                that._page = options.page || (options.pageSize ? 1 : undefined);
                that._sort = normalizeSort(options.sort);
                that._filter = normalizeFilter(options.filter);
                that._group = normalizeGroup(options.group);
                that._aggregate = options.aggregate;
                that._total = options.total;

                that._shouldDetachObservableParents = true;

                Observable.fn.init.call(that);

                that.transport = Transport.create(options, data);

                if (isFunction(that.transport.push)) {
                    that.transport.push({
                        pushCreate: proxy(that._pushCreate, that),
                        pushUpdate: proxy(that._pushUpdate, that),
                        pushDestroy: proxy(that._pushDestroy, that)
                    });
                }

                if (options.offlineStorage != null) {
                    if (typeof options.offlineStorage == "string") {
                        var key = options.offlineStorage;

                        that._storage = {
                            getItem: function () {
                                return JSON.parse(localStorage.getItem(key));
                            },
                            setItem: function (item) {
                                localStorage.setItem(key, stringify(item));
                            }
                        };
                    } else {
                        that._storage = options.offlineStorage;
                    }
                }

                that.reader = new kendo.data.readers[options.schema.type || "json"](options.schema);

                model = that.reader.model || {};

                that._detachObservableParents();

                that._data = that._observe(that._data);
                that._online = true;

                that.bind(["push", ERROR, CHANGE, REQUESTSTART, SYNC, REQUESTEND, PROGRESS], options);
            },

            options: {
                data: null,
                schema: {
                    modelBase: Model
                },
                offlineStorage: null,
                serverSorting: false,
                serverPaging: false,
                serverFiltering: false,
                serverGrouping: false,
                serverAggregates: false,
                batch: false
            },

            online: function (value) {
                if (value !== undefined) {
                    if (this._online != value) {
                        this._online = value;

                        if (value) {
                            this.sync();
                        }
                    }
                } else {
                    return this._online;
                }
            },

            offlineData: function (state) {
                if (this.options.offlineStorage == null) {
                    return null;
                }

                if (state !== undefined) {
                    return this._storage.setItem(state);
                }

                return this._storage.getItem() || {};
            },

            _isServerGrouped: function () {
                var group = this.group() || [];

                return this.options.serverGrouping && group.length;
            },

            _pushCreate: function (result) {
                this._push(result, "pushCreate");
            },

            _pushUpdate: function (result) {
                this._push(result, "pushUpdate");
            },

            _pushDestroy: function (result) {
                this._push(result, "pushDestroy");
            },

            _push: function (result, operation) {
                var data = this._readData(result);

                if (!data) {
                    data = result;
                }

                this[operation](data);
            },

            _flatData: function (data, skip) {
                if (data) {
                    if (this._isServerGrouped()) {
                        return flattenGroups(data);
                    }

                    if (!skip) {
                        for (var idx = 0; idx < data.length; idx++) {
                            data.at(idx);
                        }
                    }
                }

                return data;
            },

            parent: noop,

            get: function (id) {
                var idx, length, data = this._flatData(this._data);

                for (idx = 0, length = data.length; idx < length; idx++) {
                    if (data[idx].id == id) {
                        return data[idx];
                    }
                }
            },

            getByUid: function (id) {
                var idx, length, data = this._flatData(this._data);

                if (!data) {
                    return;
                }

                for (idx = 0, length = data.length; idx < length; idx++) {
                    if (data[idx].uid == id) {
                        return data[idx];
                    }
                }
            },

            indexOf: function (model) {
                return indexOfModel(this._data, model);
            },

            at: function (index) {
                return this._data.at(index);
            },

            data: function (value) {
                var that = this;
                if (value !== undefined) {
                    that._detachObservableParents();
                    that._data = this._observe(value);

                    that._pristineData = value.slice(0);

                    that._storeData();

                    that._ranges = [];
                    that.trigger("reset");
                    that._addRange(that._data);

                    that._total = that._data.length;
                    that._pristineTotal = that._total;

                    that._process(that._data);
                } else {
                    if (that._data) {
                        for (var idx = 0; idx < that._data.length; idx++) {
                            that._data.at(idx);
                        }
                    }

                    return that._data;
                }
            },

            view: function (value) {
                if (value === undefined) {
                    return this._view;
                } else {
                    this._view = this._observeView(value);
                }
            },

            _observeView: function (data) {
                replaceWithObservable(data, this._data, this._ranges, this.reader.model || ObservableObject);

                return new LazyObservableArray(data, this.reader.model);
            },

            flatView: function () {
                var groups = this.group() || [];

                if (groups.length) {
                    return flattenGroups(this._view);
                } else {
                    return this._view;
                }
            },

            add: function (model) {
                return this.insert(this._data.length, model);
            },

            _createNewModel: function (model) {
                if (this.reader.model) {
                    return new this.reader.model(model);
                }

                if (model instanceof ObservableObject) {
                    return model;
                }

                return new ObservableObject(model);
            },

            insert: function (index, model) {
                if (!model) {
                    model = index;
                    index = 0;
                }

                if (!(model instanceof Model)) {
                    model = this._createNewModel(model);
                }

                if (this._isServerGrouped()) {
                    this._data.splice(index, 0, wrapInEmptyGroup(this.group(), model));
                } else {
                    this._data.splice(index, 0, model);
                }

                return model;
            },

            pushCreate: function (items) {
                if (!isArray(items)) {
                    items = [items];
                }

                var pushed = [];
                var autoSync = this.options.autoSync;
                this.options.autoSync = false;

                try {
                    for (var idx = 0; idx < items.length; idx++) {
                        var item = items[idx];

                        var result = this.add(item);

                        pushed.push(result);

                        var pristine = result.toJSON();

                        if (this._isServerGrouped()) {
                            pristine = wrapInEmptyGroup(this.group(), pristine);
                        }

                        this._pristineData.push(pristine);
                    }
                } finally {
                    this.options.autoSync = autoSync;
                }

                if (pushed.length) {
                    this.trigger("push", {
                        type: "create",
                        items: pushed
                    });
                }
            },

            pushUpdate: function (items) {
                if (!isArray(items)) {
                    items = [items];
                }

                var pushed = [];

                for (var idx = 0; idx < items.length; idx++) {
                    var item = items[idx];
                    var model = this._createNewModel(item);

                    var target = this.get(model.id);

                    if (target) {
                        pushed.push(target);

                        target.accept(item);

                        target.trigger("change");

                        this._updatePristineForModel(target, item);
                    } else {
                        this.pushCreate(item);
                    }
                }

                if (pushed.length) {
                    this.trigger("push", {
                        type: "update",
                        items: pushed
                    });
                }
            },

            pushDestroy: function (items) {
                var pushed = this._removeItems(items);

                if (pushed.length) {
                    this.trigger("push", {
                        type: "destroy",
                        items: pushed
                    });
                }
            },

            _removeItems: function (items) {
                if (!isArray(items)) {
                    items = [items];
                }

                var destroyed = [];
                var autoSync = this.options.autoSync;
                this.options.autoSync = false;
                try {
                    for (var idx = 0; idx < items.length; idx++) {
                        var item = items[idx];
                        var model = this._createNewModel(item);
                        var found = false;

                        this._eachItem(this._data, function (items) {
                            for (var idx = 0; idx < items.length; idx++) {
                                var item = items.at(idx);
                                if (item.id === model.id) {
                                    destroyed.push(item);
                                    items.splice(idx, 1);
                                    found = true;
                                    break;
                                }
                            }
                        });

                        if (found) {
                            this._removePristineForModel(model);
                            this._destroyed.pop();
                        }
                    }
                } finally {
                    this.options.autoSync = autoSync;
                }

                return destroyed;
            },

            remove: function (model) {
                var result,
                that = this,
                hasGroups = that._isServerGrouped();

                this._eachItem(that._data, function (items) {
                    result = removeModel(items, model);
                    if (result && hasGroups) {
                        if (!result.isNew || !result.isNew()) {
                            that._destroyed.push(result);
                        }
                        return true;
                    }
                });

                this._removeModelFromRanges(model);

                this._updateRangesLength();

                return model;
            },

            sync: function () {
                var that = this,
                idx,
                length,
                created = [],
                updated = [],
                destroyed = that._destroyed,
                data = that._flatData(that._data);
                var promise;

                if (that.online()) {

                    if (!that.reader.model) {
                        return;
                    }

                    for (idx = 0, length = data.length; idx < length; idx++) {
                        if (data[idx].isNew()) {
                            created.push(data[idx]);
                        } else if (data[idx].dirty) {
                            updated.push(data[idx]);
                        }
                    }

                    var promises = [];
                    promises.push.apply(promises, that._send("create", created));
                    promises.push.apply(promises, that._send("update", updated));
                    promises.push.apply(promises, that._send("destroy", destroyed));

                    promise = $.when
                 .apply(null, promises)
                 .then(function () {
                     var idx, length;

                     for (idx = 0, length = arguments.length; idx < length; idx++) {
                         that._accept(arguments[idx]);
                     }

                     that._storeData(true);

                     that._change({ action: "sync" });

                     that.trigger(SYNC);
                 });
                } else {
                    that._storeData(true);

                    that._change({ action: "sync" });

                    promise = $.Deferred().resolve().promise();
                }

                return promise;
            },

            cancelChanges: function (model) {
                var that = this;

                if (model instanceof kendo.data.Model) {
                    that._cancelModel(model);
                } else {
                    that._destroyed = [];
                    that._detachObservableParents();
                    that._data = that._observe(that._pristineData);
                    if (that.options.serverPaging) {
                        that._total = that._pristineTotal;
                    }
                    that._change();
                }
            },

            hasChanges: function () {
                var idx,
                length,
                data = this._data;

                if (this._destroyed.length) {
                    return true;
                }

                for (idx = 0, length = data.length; idx < length; idx++) {
                    if (data[idx].isNew() || data[idx].dirty) {
                        return true;
                    }
                }

                return false;
            },

            _accept: function (result) {
                var that = this,
                models = result.models,
                response = result.response,
                idx = 0,
                serverGroup = that._isServerGrouped(),
                pristine = that._pristineData,
                type = result.type,
                length;

                that.trigger(REQUESTEND, { response: response, type: type });

                if (response && !isEmptyObject(response)) {
                    response = that.reader.parse(response);

                    if (that._handleCustomErrors(response)) {
                        return;
                    }

                    response = that.reader.data(response);

                    if (!isArray(response)) {
                        response = [response];
                    }
                } else {
                    response = $.map(models, function (model) { return model.toJSON(); });
                }

                if (type === "destroy") {
                    that._destroyed = [];
                }

                for (idx = 0, length = models.length; idx < length; idx++) {
                    if (type !== "destroy") {
                        models[idx].accept(response[idx]);

                        if (type === "create") {
                            pristine.push(serverGroup ? wrapInEmptyGroup(that.group(), models[idx]) : response[idx]);
                        } else if (type === "update") {
                            that._updatePristineForModel(models[idx], response[idx]);
                        }
                    } else {
                        that._removePristineForModel(models[idx]);
                    }
                }
            },

            _updatePristineForModel: function (model, values) {
                this._executeOnPristineForModel(model, function (index, items) {
                    kendo.deepExtend(items[index], values);
                });
            },

            _executeOnPristineForModel: function (model, callback) {
                this._eachPristineItem(
                function (items) {
                    var index = indexOfPristineModel(items, model);
                    if (index > -1) {
                        callback(index, items);
                        return true;
                    }
                });
            },

            _removePristineForModel: function (model) {
                this._executeOnPristineForModel(model, function (index, items) {
                    items.splice(index, 1);
                });
            },

            _readData: function (data) {
                var read = !this._isServerGrouped() ? this.reader.data : this.reader.groups;
                return read.call(this.reader, data);
            },

            _eachPristineItem: function (callback) {
                this._eachItem(this._pristineData, callback);
            },

            _eachItem: function (data, callback) {
                if (data && data.length) {
                    if (this._isServerGrouped()) {
                        eachGroupItems(data, callback);
                    } else {
                        callback(data);
                    }
                }
            },

            _pristineForModel: function (model) {
                var pristine,
                idx,
                callback = function (items) {
                    idx = indexOfPristineModel(items, model);
                    if (idx > -1) {
                        pristine = items[idx];
                        return true;
                    }
                };

                this._eachPristineItem(callback);

                return pristine;
            },

            _cancelModel: function (model) {
                var pristine = this._pristineForModel(model);

                this._eachItem(this._data, function (items) {
                    var idx = indexOfModel(items, model);
                    if (idx >= 0) {
                        if (pristine && (!model.isNew() || pristine.__state__)) {
                            items[idx].accept(pristine);
                        } else {
                            items.splice(idx, 1);
                        }
                    }
                });
            },

            _promise: function (data, models, type) {
                var that = this;

                return $.Deferred(function (deferred) {
                    that.trigger(REQUESTSTART, { type: type });

                    that.transport[type].call(that.transport, extend({
                        success: function (response) {
                            deferred.resolve({
                                response: response,
                                models: models,
                                type: type
                            });
                        },
                        error: function (response, status, error) {
                            deferred.reject(response);
                            that.error(response, status, error);
                        }
                    }, data));
                }).promise();
            },

            _send: function (method, data) {
                var that = this,
                idx,
                length,
                promises = [],
                converted = that.reader.serialize(toJSON(data));

                if (that.options.batch) {
                    if (data.length) {
                        promises.push(that._promise({ data: { models: converted} }, data, method));
                    }
                } else {
                    for (idx = 0, length = data.length; idx < length; idx++) {
                        promises.push(that._promise({ data: converted[idx] }, [data[idx]], method));
                    }
                }

                return promises;
            },

            read: function (data) {
                var that = this, params = that._params(data);
                var deferred = $.Deferred();

                that._queueRequest(params, function () {
                    if (!that.trigger(REQUESTSTART, { type: "read" })) {
                        that.trigger(PROGRESS);

                        that._ranges = [];
                        that.trigger("reset");
                        if (that.online()) {
                            that.transport.read({
                                data: params,
                                success: function (data) {
                                    that.success(data);

                                    deferred.resolve();

                                    //BEGIN EDIT: (3GiG: 2016-12-06)
                                    if (params.success != null) {
                                        params.success();
                                    }
                                    //END EDIT: (3GiG: 2016-12-06)

                                },
                                error: function () {
                                    var args = slice.call(arguments);

                                    that.error.apply(that, args);

                                    deferred.reject.apply(deferred, args);

                                    //BEGIN EDIT: (3GiG: 2016-12-06)
                                    if (params.error != null) {
                                        params.error();
                                    }
                                    //END EDIT: (3GiG: 2016-12-06)

                                }
                            });
                        } else if (that.options.offlineStorage != null) {
                            that.success(that.offlineData());

                            deferred.resolve();
                        }
                    } else {
                        that._dequeueRequest();

                        deferred.resolve();
                    }
                });

                return deferred.promise();
            },

            _readAggregates: function (data) {
                return this.reader.aggregates(data);
            },

            success: function (data) {
                var that = this,
                options = that.options;

                that.trigger(REQUESTEND, { response: data, type: "read" });

                if (that.online()) {
                    data = that.reader.parse(data);

                    if (that._handleCustomErrors(data)) {
                        that._dequeueRequest();
                        return;
                    }

                    that._total = that.reader.total(data);

                    if (that._aggregate && options.serverAggregates) {
                        that._aggregateResult = that._readAggregates(data);
                    }

                    data = that._readData(data);
                } else {
                    data = that._readData(data);

                    var items = [];

                    for (var idx = 0; idx < data.length; idx++) {
                        var item = data[idx];
                        var state = item.__state__;

                        if (state == "destroy") {
                            this._destroyed.push(this._createNewModel(item));
                        } else {
                            items.push(item);
                        }
                    }

                    data = items;

                    that._total = data.length;
                }

                that._pristineTotal = that._total;

                that._pristineData = data.slice(0);

                that._detachObservableParents();

                that._data = that._observe(data);

                if (that.options.offlineStorage != null) {
                    that._eachItem(that._data, function (items) {
                        for (var idx = 0; idx < items.length; idx++) {
                            var item = items.at(idx);
                            if (item.__state__ == "update") {
                                item.dirty = true;
                            }
                        }
                    });
                }

                that._storeData();

                that._addRange(that._data);

                that._process(that._data);

                that._dequeueRequest();
            },

            _detachObservableParents: function () {
                if (this._data && this._shouldDetachObservableParents) {
                    for (var idx = 0; idx < this._data.length; idx++) {
                        if (this._data[idx].parent) {
                            this._data[idx].parent = noop;
                        }
                    }
                }
            },

            _storeData: function (updatePristine) {
                var serverGrouping = this._isServerGrouped();
                var model = this.reader.model;

                function items(data) {
                    var state = [];

                    for (var idx = 0; idx < data.length; idx++) {
                        var dataItem = data.at(idx);
                        var item = dataItem.toJSON();

                        if (serverGrouping && dataItem.items) {
                            item.items = items(dataItem.items);
                        } else {
                            item.uid = dataItem.uid;

                            if (model) {
                                if (dataItem.isNew()) {
                                    item.__state__ = "create";
                                } else if (dataItem.dirty) {
                                    item.__state__ = "update";
                                }
                            }
                        }
                        state.push(item);
                    }

                    return state;
                }

                if (this.options.offlineStorage != null) {
                    var state = items(this._data);

                    for (var idx = 0; idx < this._destroyed.length; idx++) {
                        var item = this._destroyed[idx].toJSON();
                        item.__state__ = "destroy";
                        state.push(item);
                    }

                    this.offlineData(state);

                    if (updatePristine) {
                        this._pristineData = state;
                    }
                }
            },

            _addRange: function (data) {
                var that = this,
                start = that._skip || 0,
                end = start + that._flatData(data, true).length;

                that._ranges.push({ start: start, end: end, data: data });
                that._ranges.sort(function (x, y) { return x.start - y.start; });
            },

            error: function (xhr, status, errorThrown) {
                this._dequeueRequest();
                this.trigger(REQUESTEND, {});
                this.trigger(ERROR, { xhr: xhr, status: status, errorThrown: errorThrown });
            },

            _params: function (data) {
                var that = this,
                options = extend({
                    take: that.take(),
                    skip: that.skip(),
                    page: that.page(),
                    pageSize: that.pageSize(),
                    sort: that._sort,
                    filter: that._filter,
                    group: that._group,
                    aggregate: that._aggregate
                }, data);

                if (!that.options.serverPaging) {
                    delete options.take;
                    delete options.skip;
                    delete options.page;
                    delete options.pageSize;
                }

                if (!that.options.serverGrouping) {
                    delete options.group;
                } else if (that.reader.model && options.group) {
                    options.group = convertDescriptorsField(options.group, that.reader.model);
                }

                if (!that.options.serverFiltering) {
                    delete options.filter;
                } else if (that.reader.model && options.filter) {
                    options.filter = convertFilterDescriptorsField(options.filter, that.reader.model);
                }

                if (!that.options.serverSorting) {
                    delete options.sort;
                } else if (that.reader.model && options.sort) {
                    options.sort = convertDescriptorsField(options.sort, that.reader.model);
                }

                if (!that.options.serverAggregates) {
                    delete options.aggregate;
                } else if (that.reader.model && options.aggregate) {
                    options.aggregate = convertDescriptorsField(options.aggregate, that.reader.model);
                }

                return options;
            },

            _queueRequest: function (options, callback) {
                var that = this;
                if (!that._requestInProgress) {
                    that._requestInProgress = true;
                    that._pending = undefined;
                    callback();
                } else {
                    that._pending = { callback: proxy(callback, that), options: options };
                }
            },

            _dequeueRequest: function () {
                var that = this;
                that._requestInProgress = false;
                if (that._pending) {
                    that._queueRequest(that._pending.options, that._pending.callback);
                }
            },

            _handleCustomErrors: function (response) {
                if (this.reader.errors) {
                    var errors = this.reader.errors(response);
                    if (errors) {
                        this.trigger(ERROR, { xhr: null, status: "customerror", errorThrown: "custom error", errors: errors });
                        return true;
                    }
                }
                return false;
            },

            _observe: function (data) {
                var that = this,
                model = that.reader.model,
                wrap = false;

                that._shouldDetachObservableParents = true;

                if (model && data.length) {
                    wrap = !(data[0] instanceof model);
                }

                if (data instanceof ObservableArray) {
                    that._shouldDetachObservableParents = false;
                    if (wrap) {
                        data.type = that.reader.model;
                        data.wrapAll(data, data);
                    }
                } else {
                    var arrayType = that.pageSize() ? LazyObservableArray : ObservableArray;
                    data = new arrayType(data, that.reader.model);
                    data.parent = function () { return that.parent(); };
                }

                if (that._isServerGrouped()) {
                    wrapGroupItems(data, model);
                }

                if (that._changeHandler && that._data && that._data instanceof ObservableArray) {
                    that._data.unbind(CHANGE, that._changeHandler);
                } else {
                    that._changeHandler = proxy(that._change, that);
                }

                return data.bind(CHANGE, that._changeHandler);
            },

            _change: function (e) {
                var that = this, idx, length, action = e ? e.action : "";

                if (action === "remove") {
                    for (idx = 0, length = e.items.length; idx < length; idx++) {
                        if (!e.items[idx].isNew || !e.items[idx].isNew()) {
                            that._destroyed.push(e.items[idx]);
                        }
                    }
                }

                if (that.options.autoSync && (action === "add" || action === "remove" || action === "itemchange")) {
                    that.sync();
                } else {
                    var total = parseInt(that._total, 10);
                    if (!isNumber(that._total)) {
                        total = parseInt(that._pristineTotal, 10);
                    }
                    if (action === "add") {
                        total += e.items.length;
                    } else if (action === "remove") {
                        total -= e.items.length;
                    } else if (action !== "itemchange" && action !== "sync" && !that.options.serverPaging) {
                        total = that._pristineTotal;
                    } else if (action === "sync") {
                        total = that._pristineTotal = parseInt(that._total, 10);
                    }

                    that._total = total;

                    that._process(that._data, e);
                }
            },

            _calculateAggregates: function (data, options) {
                options = options || {};

                var query = new Query(data),
                aggregates = options.aggregate,
                filter = options.filter;

                if (filter) {
                    query = query.filter(filter);
                }

                return query.aggregate(aggregates);
            },

            _process: function (data, e) {
                var that = this,
                options = {},
                result;

                if (that.options.serverPaging !== true) {
                    options.skip = that._skip;
                    options.take = that._take || that._pageSize;

                    if (options.skip === undefined && that._page !== undefined && that._pageSize !== undefined) {
                        options.skip = (that._page - 1) * that._pageSize;
                    }
                }

                if (that.options.serverSorting !== true) {
                    options.sort = that._sort;
                }

                if (that.options.serverFiltering !== true) {
                    options.filter = that._filter;
                }

                if (that.options.serverGrouping !== true) {
                    options.group = that._group;
                }

                if (that.options.serverAggregates !== true) {
                    options.aggregate = that._aggregate;
                    that._aggregateResult = that._calculateAggregates(data, options);
                }

                result = that._queryProcess(data, options);

                that.view(result.data);

                if (result.total !== undefined && !that.options.serverFiltering) {
                    that._total = result.total;
                }

                e = e || {};

                e.items = e.items || that._view;

                that.trigger(CHANGE, e);
            },

            _queryProcess: function (data, options) {
                return Query.process(data, options);
            },

            _mergeState: function (options) {
                var that = this;

                if (options !== undefined) {
                    that._pageSize = options.pageSize;
                    that._page = options.page;
                    that._sort = options.sort;
                    that._filter = options.filter;
                    that._group = options.group;
                    that._aggregate = options.aggregate;
                    that._skip = options.skip;
                    that._take = options.take;

                    if (that._skip === undefined) {
                        that._skip = that.skip();
                        options.skip = that.skip();
                    }

                    if (that._take === undefined && that._pageSize !== undefined) {
                        that._take = that._pageSize;
                        options.take = that._take;
                    }

                    if (options.sort) {
                        that._sort = options.sort = normalizeSort(options.sort);
                    }

                    if (options.filter) {
                        that._filter = options.filter = normalizeFilter(options.filter);
                    }

                    if (options.group) {
                        that._group = options.group = normalizeGroup(options.group);
                    }
                    if (options.aggregate) {
                        that._aggregate = options.aggregate = normalizeAggregate(options.aggregate);
                    }
                }
                return options;
            },

            query: function (options) {
                var result;
                var remote = this.options.serverSorting || this.options.serverPaging || this.options.serverFiltering || this.options.serverGrouping || this.options.serverAggregates;

                if (remote || ((this._data === undefined || this._data.length === 0) && !this._destroyed.length)) {
                    return this.read(this._mergeState(options));
                }

                if (!this.trigger(REQUESTSTART, { type: "read" })) {
                    this.trigger(PROGRESS);

                    result = this._queryProcess(this._data, this._mergeState(options));

                    if (!this.options.serverFiltering) {
                        if (result.total !== undefined) {
                            this._total = result.total;
                        } else {
                            this._total = this._data.length;
                        }
                    }

                    this._aggregateResult = this._calculateAggregates(this._data, options);
                    this.view(result.data);
                    this.trigger(REQUESTEND, {});
                    this.trigger(CHANGE, { items: result.data });
                }

                return $.Deferred().resolve().promise();
            },

            fetch: function (callback) {
                return this._query().then(proxy(callback, this));
            },

            _query: function (options) {
                var that = this;

                return that.query(extend({}, {
                    page: that.page(),
                    pageSize: that.pageSize(),
                    sort: that.sort(),
                    filter: that.filter(),
                    group: that.group(),
                    aggregate: that.aggregate()
                }, options));
            },

            next: function (options) {
                var that = this,
                page = that.page(),
                total = that.total();

                options = options || {};

                if (!page || (total && page + 1 > that.totalPages())) {
                    return;
                }

                that._skip = page * that.take();

                page += 1;
                options.page = page;

                that._query(options);

                return page;
            },

            prev: function (options) {
                var that = this,
                page = that.page();

                options = options || {};

                if (!page || page === 1) {
                    return;
                }

                that._skip = that._skip - that.take();

                page -= 1;
                options.page = page;

                that._query(options);

                return page;
            },

            page: function (val) {
                var that = this,
            skip;

                if (val !== undefined) {
                    val = math.max(math.min(math.max(val, 1), that.totalPages()), 1);
                    that._query({ page: val });
                    return;
                }
                skip = that.skip();

                return skip !== undefined ? math.round((skip || 0) / (that.take() || 1)) + 1 : undefined;
            },

            pageSize: function (val) {
                var that = this;

                if (val !== undefined) {
                    that._query({ pageSize: val, page: 1 });
                    return;
                }

                return that.take();
            },

            sort: function (val) {
                var that = this;

                if (val !== undefined) {
                    that._query({ sort: val });
                    return;
                }

                return that._sort;
            },

            filter: function (val) {
                var that = this;

                if (val === undefined) {
                    return that._filter;
                }

                that._query({ filter: val, page: 1 });
            },

            group: function (val) {
                var that = this;

                if (val !== undefined) {
                    that._query({ group: val });
                    return;
                }

                return that._group;
            },

            total: function () {
                return parseInt(this._total || 0, 10);
            },

            aggregate: function (val) {
                var that = this;

                if (val !== undefined) {
                    that._query({ aggregate: val });
                    return;
                }

                return that._aggregate;
            },

            aggregates: function () {
                return this._aggregateResult;
            },

            totalPages: function () {
                var that = this,
            pageSize = that.pageSize() || that.total();

                return math.ceil((that.total() || 0) / pageSize);
            },

            inRange: function (skip, take) {
                var that = this,
                end = math.min(skip + take, that.total());

                if (!that.options.serverPaging && that._data.length > 0) {
                    return true;
                }

                return that._findRange(skip, end).length > 0;
            },

            lastRange: function () {
                var ranges = this._ranges;
                return ranges[ranges.length - 1] || { start: 0, end: 0, data: [] };
            },

            firstItemUid: function () {
                var ranges = this._ranges;
                return ranges.length && ranges[0].data.length && ranges[0].data[0].uid;
            },

            enableRequestsInProgress: function () {
                this._skipRequestsInProgress = false;
            },

            range: function (skip, take) {
                skip = math.min(skip || 0, this.total());

                var that = this,
                pageSkip = math.max(math.floor(skip / take), 0) * take,
                size = math.min(pageSkip + take, that.total()),
                data;

                that._skipRequestsInProgress = false;

                data = that._findRange(skip, math.min(skip + take, that.total()));

                if (data.length) {

                    that._skipRequestsInProgress = true;
                    that._pending = undefined;

                    that._skip = skip > that.skip() ? math.min(size, (that.totalPages() - 1) * that.take()) : pageSkip;

                    that._take = take;

                    var paging = that.options.serverPaging;
                    var sorting = that.options.serverSorting;
                    var filtering = that.options.serverFiltering;
                    var aggregates = that.options.serverAggregates;
                    try {
                        that.options.serverPaging = true;
                        if (!that._isServerGrouped() && !(that.group() && that.group().length)) {
                            that.options.serverSorting = true;
                        }
                        that.options.serverFiltering = true;
                        that.options.serverPaging = true;
                        that.options.serverAggregates = true;

                        if (paging) {
                            that._detachObservableParents();
                            that._data = data = that._observe(data);
                        }
                        that._process(data);
                    } finally {
                        that.options.serverPaging = paging;
                        that.options.serverSorting = sorting;
                        that.options.serverFiltering = filtering;
                        that.options.serverAggregates = aggregates;
                    }

                    return;
                }

                if (take !== undefined) {
                    if (!that._rangeExists(pageSkip, size)) {
                        that.prefetch(pageSkip, take, function () {
                            if (skip > pageSkip && size < that.total() && !that._rangeExists(size, math.min(size + take, that.total()))) {
                                that.prefetch(size, take, function () {
                                    that.range(skip, take);
                                });
                            } else {
                                that.range(skip, take);
                            }
                        });
                    } else if (pageSkip < skip) {
                        that.prefetch(size, take, function () {
                            that.range(skip, take);
                        });
                    }
                }
            },

            _findRange: function (start, end) {
                var that = this,
                ranges = that._ranges,
                range,
                data = [],
                skipIdx,
                takeIdx,
                startIndex,
                endIndex,
                rangeData,
                rangeEnd,
                processed,
                options = that.options,
                remote = options.serverSorting || options.serverPaging || options.serverFiltering || options.serverGrouping || options.serverAggregates,
                flatData,
                count,
                length;

                for (skipIdx = 0, length = ranges.length; skipIdx < length; skipIdx++) {
                    range = ranges[skipIdx];
                    if (start >= range.start && start <= range.end) {
                        count = 0;

                        for (takeIdx = skipIdx; takeIdx < length; takeIdx++) {
                            range = ranges[takeIdx];
                            flatData = that._flatData(range.data, true);

                            if (flatData.length && start + count >= range.start) {
                                rangeData = range.data;
                                rangeEnd = range.end;

                                if (!remote) {
                                    var sort = normalizeGroup(that.group() || []).concat(normalizeSort(that.sort() || []));
                                    processed = that._queryProcess(range.data, { sort: sort, filter: that.filter() });
                                    flatData = rangeData = processed.data;

                                    if (processed.total !== undefined) {
                                        rangeEnd = processed.total;
                                    }
                                }

                                startIndex = 0;
                                if (start + count > range.start) {
                                    startIndex = (start + count) - range.start;
                                }
                                endIndex = flatData.length;
                                if (rangeEnd > end) {
                                    endIndex = endIndex - (rangeEnd - end);
                                }
                                count += endIndex - startIndex;
                                data = that._mergeGroups(data, rangeData, startIndex, endIndex);

                                if (end <= range.end && count == end - start) {
                                    return data;
                                }
                            }
                        }
                        break;
                    }
                }
                return [];
            },

            _mergeGroups: function (data, range, skip, take) {
                if (this._isServerGrouped()) {
                    var temp = range.toJSON(),
                    prevGroup;

                    if (data.length) {
                        prevGroup = data[data.length - 1];
                    }

                    mergeGroups(prevGroup, temp, skip, take);

                    return data.concat(temp);
                }
                return data.concat(range.slice(skip, take));
            },

            skip: function () {
                var that = this;

                if (that._skip === undefined) {
                    return (that._page !== undefined ? (that._page - 1) * (that.take() || 1) : undefined);
                }
                return that._skip;
            },

            take: function () {
                return this._take || this._pageSize;
            },

            _prefetchSuccessHandler: function (skip, size, callback) {
                var that = this;

                return function (data) {
                    var found = false,
                    range = { start: skip, end: size, data: [] },
                    idx,
                    length,
                    temp;

                    that._dequeueRequest();

                    that.trigger(REQUESTEND, { response: data, type: "read" });

                    data = that.reader.parse(data);

                    temp = that._readData(data);

                    if (temp.length) {
                        for (idx = 0, length = that._ranges.length; idx < length; idx++) {
                            if (that._ranges[idx].start === skip) {
                                found = true;
                                range = that._ranges[idx];
                                break;
                            }
                        }
                        if (!found) {
                            that._ranges.push(range);
                        }
                    }

                    range.data = that._observe(temp);
                    range.end = range.start + that._flatData(range.data, true).length;
                    that._ranges.sort(function (x, y) { return x.start - y.start; });
                    that._total = that.reader.total(data);

                    if (!that._skipRequestsInProgress) {
                        if (callback && temp.length) {
                            callback();
                        } else {
                            that.trigger(CHANGE, {});
                        }
                    }
                };
            },

            prefetch: function (skip, take, callback) {
                var that = this,
                size = math.min(skip + take, that.total()),
                options = {
                    take: take,
                    skip: skip,
                    page: skip / take + 1,
                    pageSize: take,
                    sort: that._sort,
                    filter: that._filter,
                    group: that._group,
                    aggregate: that._aggregate
                };

                if (!that._rangeExists(skip, size)) {
                    clearTimeout(that._timeout);

                    that._timeout = setTimeout(function () {
                        that._queueRequest(options, function () {
                            if (!that.trigger(REQUESTSTART, { type: "read" })) {
                                that.transport.read({
                                    data: that._params(options),
                                    success: that._prefetchSuccessHandler(skip, size, callback)
                                });
                            } else {
                                that._dequeueRequest();
                            }
                        });
                    }, 100);
                } else if (callback) {
                    callback();
                }
            },

            _rangeExists: function (start, end) {
                var that = this,
                ranges = that._ranges,
                idx,
                length;

                for (idx = 0, length = ranges.length; idx < length; idx++) {
                    if (ranges[idx].start <= start && ranges[idx].end >= end) {
                        return true;
                    }
                }
                return false;
            },

            _removeModelFromRanges: function (model) {
                var result,
                found,
                range;

                for (var idx = 0, length = this._ranges.length; idx < length; idx++) {
                    range = this._ranges[idx];

                    this._eachItem(range.data, function (items) {
                        result = removeModel(items, model);
                        if (result) {
                            found = true;
                        }
                    });

                    if (found) {
                        break;
                    }
                }
            },

            _updateRangesLength: function () {
                var startOffset = 0,
                range,
                rangeLength;

                for (var idx = 0, length = this._ranges.length; idx < length; idx++) {
                    range = this._ranges[idx];
                    range.start = range.start - startOffset;

                    rangeLength = this._flatData(range.data, true).length;
                    startOffset = range.end - rangeLength;
                    range.end = range.start + rangeLength;
                }
            }
        });

        var Transport = {};

        Transport.create = function (options, data) {
            var transport,
            transportOptions = options.transport;

            if (transportOptions) {
                transportOptions.read = typeof transportOptions.read === STRING ? { url: transportOptions.read} : transportOptions.read;

                if (options.type) {
                    kendo.data.transports = kendo.data.transports || {};
                    kendo.data.schemas = kendo.data.schemas || {};

                    if (kendo.data.transports[options.type] && !isPlainObject(kendo.data.transports[options.type])) {
                        transport = new kendo.data.transports[options.type](extend(transportOptions, { data: data }));
                    } else {
                        transportOptions = extend(true, {}, kendo.data.transports[options.type], transportOptions);
                    }

                    options.schema = extend(true, {}, kendo.data.schemas[options.type], options.schema);
                }

                if (!transport) {
                    transport = isFunction(transportOptions.read) ? transportOptions : new RemoteTransport(transportOptions);
                }
            } else {
                transport = new LocalTransport({ data: options.data || [] });
            }
            return transport;
        };

        DataSource.create = function (options) {
            if (isArray(options) || options instanceof ObservableArray) {
                options = { data: options };
            }

            var dataSource = options || {},
            data = dataSource.data,
            fields = dataSource.fields,
            table = dataSource.table,
            select = dataSource.select,
            idx,
            length,
            model = {},
            field;

            if (!data && fields && !dataSource.transport) {
                if (table) {
                    data = inferTable(table, fields);
                } else if (select) {
                    data = inferSelect(select, fields);
                }
            }

            if (kendo.data.Model && fields && (!dataSource.schema || !dataSource.schema.model)) {
                for (idx = 0, length = fields.length; idx < length; idx++) {
                    field = fields[idx];
                    if (field.type) {
                        model[field.field] = field;
                    }
                }

                if (!isEmptyObject(model)) {
                    dataSource.schema = extend(true, dataSource.schema, { model: { fields: model} });
                }
            }

            dataSource.data = data;
            table = null;
            dataSource.table = null;

            return dataSource instanceof DataSource ? dataSource : new DataSource(dataSource);
        };

        function inferSelect(select, fields) {
            var options = $(select)[0].children,
            idx,
            length,
            data = [],
            record,
            firstField = fields[0],
            secondField = fields[1],
            value,
            option;

            for (idx = 0, length = options.length; idx < length; idx++) {
                record = {};
                option = options[idx];

                if (option.disabled) {
                    continue;
                }

                record[firstField.field] = option.text;

                value = option.attributes.value;

                if (value && value.specified) {
                    value = option.value;
                } else {
                    value = option.text;
                }

                record[secondField.field] = value;

                data.push(record);
            }

            return data;
        }

        function inferTable(table, fields) {
            var tbody = $(table)[0].tBodies[0],
        rows = tbody ? tbody.rows : [],
        idx,
        length,
        fieldIndex,
        fieldCount = fields.length,
        data = [],
        cells,
        record,
        cell,
        empty;

            for (idx = 0, length = rows.length; idx < length; idx++) {
                record = {};
                empty = true;
                cells = rows[idx].cells;

                for (fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
                    cell = cells[fieldIndex];
                    if (cell.nodeName.toLowerCase() !== "th") {
                        empty = false;
                        record[fields[fieldIndex].field] = cell.innerHTML;
                    }
                }
                if (!empty) {
                    data.push(record);
                }
            }

            return data;
        }

        var Node = Model.define({
            id: "id",

            init: function (value) {
                var that = this,
                hasChildren = that.hasChildren || value && value.hasChildren,
                childrenField = "items",
                childrenOptions = {};

                kendo.data.Model.fn.init.call(that, value);

                if (typeof that.children === STRING) {
                    childrenField = that.children;
                }

                childrenOptions = {
                    schema: {
                        data: childrenField,
                        model: {
                            hasChildren: hasChildren,
                            id: that.idField,
                            fields: that.fields
                        }
                    }
                };

                if (typeof that.children !== STRING) {
                    extend(childrenOptions, that.children);
                }

                childrenOptions.data = value;

                if (!hasChildren) {
                    hasChildren = childrenOptions.schema.data;
                }

                if (typeof hasChildren === STRING) {
                    hasChildren = kendo.getter(hasChildren);
                }

                if (isFunction(hasChildren)) {
                    that.hasChildren = !!hasChildren.call(that, that);
                }

                that._childrenOptions = childrenOptions;

                if (that.hasChildren) {
                    that._initChildren();
                }

                that._loaded = !!(value && (value[childrenField] || value._loaded));
            },

            _initChildren: function () {
                var that = this;
                var children, transport, parameterMap;

                if (!(that.children instanceof HierarchicalDataSource)) {
                    children = that.children = new HierarchicalDataSource(that._childrenOptions);

                    transport = children.transport;
                    parameterMap = transport.parameterMap;

                    transport.parameterMap = function (data, type) {
                        data[that.idField || "id"] = that.id;

                        if (parameterMap) {
                            data = parameterMap(data, type);
                        }

                        return data;
                    };

                    children.parent = function () {
                        return that;
                    };

                    children.bind(CHANGE, function (e) {
                        e.node = e.node || that;
                        that.trigger(CHANGE, e);
                    });

                    children.bind(ERROR, function (e) {
                        var collection = that.parent();

                        if (collection) {
                            e.node = e.node || that;
                            collection.trigger(ERROR, e);
                        }
                    });

                    that._updateChildrenField();
                }
            },

            append: function (model) {
                this._initChildren();
                this.loaded(true);
                this.children.add(model);
            },

            hasChildren: false,

            level: function () {
                var parentNode = this.parentNode(),
                level = 0;

                while (parentNode && parentNode.parentNode) {
                    level++;
                    parentNode = parentNode.parentNode ? parentNode.parentNode() : null;
                }

                return level;
            },

            _updateChildrenField: function () {
                var fieldName = this._childrenOptions.schema.data;

                this[fieldName || "items"] = this.children.data();
            },

            _childrenLoaded: function () {
                this._loaded = true;

                this._updateChildrenField();
            },

            load: function (success, error) {
                var options = {};
                var method = "_query";
                var children;

                if (this.hasChildren) {
                    this._initChildren();

                    children = this.children;

                    //BEGIN EDIT: (3GiG: 2013-07-06)
                    options["dataItem"] = this;
                    options["success"] = success;
                    options["error"] = success;
                    //END EDIT

                    options[this.idField || "id"] = this.id;

                    if (!this._loaded) {
                        children._data = undefined;
                        method = "read";
                    }

                    children.one(CHANGE, proxy(this._childrenLoaded, this));
                    children[method](options);
                } else {
                    this.loaded(true);
                }
            },

            parentNode: function () {
                var array = this.parent();

                return array.parent();
            },

            loaded: function (value) {
                if (value !== undefined) {
                    this._loaded = value;
                } else {
                    return this._loaded;
                }
            },

            shouldSerialize: function (field) {
                return Model.fn.shouldSerialize.call(this, field) &&
                    field !== "children" &&
                    field !== "_loaded" &&
                    field !== "hasChildren" &&
                    field !== "_childrenOptions";
            }
        });

        function dataMethod(name) {
            return function () {
                var data = this._data,
                result = DataSource.fn[name].apply(this, slice.call(arguments));

                if (this._data != data) {
                    this._attachBubbleHandlers();
                }

                return result;
            };
        }

        var HierarchicalDataSource = DataSource.extend({
            init: function (options) {
                var node = Node.define({
                    children: options
                });

                DataSource.fn.init.call(this, extend(true, {}, { schema: { modelBase: node, model: node} }, options));

                this._attachBubbleHandlers();
            },

            _attachBubbleHandlers: function () {
                var that = this;

                that._data.bind(ERROR, function (e) {
                    that.trigger(ERROR, e);
                });
            },

            remove: function (node) {
                var parentNode = node.parentNode(),
                dataSource = this,
                result;

                if (parentNode && parentNode._initChildren) {
                    dataSource = parentNode.children;
                }

                result = DataSource.fn.remove.call(dataSource, node);

                if (parentNode && !dataSource.data().length) {
                    parentNode.hasChildren = false;
                }

                return result;
            },

            success: dataMethod("success"),

            data: dataMethod("data"),

            insert: function (index, model) {
                var parentNode = this.parent();

                if (parentNode && parentNode._initChildren) {
                    parentNode.hasChildren = true;
                    parentNode._initChildren();
                }

                return DataSource.fn.insert.call(this, index, model);
            },

            _find: function (method, value) {
                var idx, length, node, data, children;

                node = DataSource.fn[method].call(this, value);

                if (node) {
                    return node;
                }

                data = this._flatData(this._data);

                if (!data) {
                    return;
                }

                for (idx = 0, length = data.length; idx < length; idx++) {
                    children = data[idx].children;

                    if (!(children instanceof HierarchicalDataSource)) {
                        continue;
                    }

                    node = children[method](value);

                    if (node) {
                        return node;
                    }
                }
            },

            get: function (id) {
                return this._find("get", id);
            },

            getByUid: function (uid) {
                return this._find("getByUid", uid);
            }
        });

        function inferList(list, fields) {
            var items = $(list).children(),
            idx,
            length,
            data = [],
            record,
            textField = fields[0].field,
            urlField = fields[1] && fields[1].field,
            spriteCssClassField = fields[2] && fields[2].field,
            imageUrlField = fields[3] && fields[3].field,
            item,
            id,
            textChild,
            className,
            children;

            function elements(collection, tagName) {
                return collection.filter(tagName).add(collection.find(tagName));
            }

            for (idx = 0, length = items.length; idx < length; idx++) {
                record = { _loaded: true };
                item = items.eq(idx);

                textChild = item[0].firstChild;
                children = item.children();
                list = children.filter("ul");
                children = children.filter(":not(ul)");

                id = item.attr("data-id");

                if (id) {
                    record.id = id;
                }

                if (textChild) {
                    record[textField] = textChild.nodeType == 3 ? textChild.nodeValue : children.text();
                }

                if (urlField) {
                    record[urlField] = elements(children, "a").attr("href");
                }

                if (imageUrlField) {
                    record[imageUrlField] = elements(children, "img").attr("src");
                }

                if (spriteCssClassField) {
                    className = elements(children, ".k-sprite").prop("className");
                    record[spriteCssClassField] = className && $.trim(className.replace("k-sprite", ""));
                }

                if (list.length) {
                    record.items = inferList(list.eq(0), fields);
                }

                if (item.attr("data-hasChildren") == "true") {
                    record.hasChildren = true;
                }

                data.push(record);
            }

            return data;
        }

        HierarchicalDataSource.create = function (options) {
            options = options && options.push ? { data: options} : options;

            var dataSource = options || {},
            data = dataSource.data,
            fields = dataSource.fields,
            list = dataSource.list;

            if (data && data._dataSource) {
                return data._dataSource;
            }

            if (!data && fields && !dataSource.transport) {
                if (list) {
                    data = inferList(list, fields);
                }
            }

            dataSource.data = data;

            return dataSource instanceof HierarchicalDataSource ? dataSource : new HierarchicalDataSource(dataSource);
        };

        var Buffer = kendo.Observable.extend({
            init: function (dataSource, viewSize, disablePrefetch) {
                kendo.Observable.fn.init.call(this);

                this._prefetching = false;
                this.dataSource = dataSource;
                this.prefetch = !disablePrefetch;

                var buffer = this;

                dataSource.bind("change", function () {
                    buffer._change();
                });

                dataSource.bind("reset", function () {
                    buffer._reset();
                });

                this._syncWithDataSource();

                this.setViewSize(viewSize);
            },

            setViewSize: function (viewSize) {
                this.viewSize = viewSize;
                this._recalculate();
            },

            at: function (index) {
                var pageSize = this.pageSize,
                item,
                itemPresent = true,
                changeTo;

                if (index >= this.total()) {
                    this.trigger("endreached", { index: index });
                    return;
                }

                if (!this.useRanges) {
                    return this.dataSource.view()[index];
                }
                if (this.useRanges) {
                    // out of range request
                    if (index < this.dataOffset || index >= this.skip + pageSize) {
                        itemPresent = this.range(Math.floor(index / pageSize) * pageSize);
                    }

                    // prefetch
                    if (index === this.prefetchThreshold) {
                        this._prefetch();
                    }

                    // mid-range jump - prefetchThreshold and nextPageThreshold may be equal, do not change to else if
                    if (index === this.midPageThreshold) {
                        this.range(this.nextMidRange, true);
                    }
                    // next range jump
                    else if (index === this.nextPageThreshold) {
                        this.range(this.nextFullRange);
                    }
                    // pull-back
                    else if (index === this.pullBackThreshold) {
                        if (this.offset === this.skip) { // from full range to mid range
                            this.range(this.previousMidRange);
                        } else { // from mid range to full range
                            this.range(this.previousFullRange);
                        }
                    }

                    if (itemPresent) {
                        return this.dataSource.at(index - this.dataOffset);
                    } else {
                        this.trigger("endreached", { index: index });
                        return null;
                    }
                }
            },

            indexOf: function (item) {
                return this.dataSource.data().indexOf(item) + this.dataOffset;
            },

            total: function () {
                return parseInt(this.dataSource.total(), 10);
            },

            next: function () {
                var buffer = this,
                pageSize = buffer.pageSize,
                offset = buffer.skip - buffer.viewSize + pageSize,
                pageSkip = math.max(math.floor(offset / pageSize), 0) * pageSize;

                this.offset = offset;
                this.dataSource.prefetch(pageSkip, pageSize, function () {
                    buffer._goToRange(offset, true);
                });
            },

            range: function (offset, nextRange) {
                if (this.offset === offset) {
                    return true;
                }

                var buffer = this,
                pageSize = this.pageSize,
                pageSkip = math.max(math.floor(offset / pageSize), 0) * pageSize,
                dataSource = this.dataSource;

                if (nextRange) {
                    pageSkip += pageSize;
                }

                if (dataSource.inRange(offset, pageSize)) {
                    this.offset = offset;
                    this._recalculate();
                    this._goToRange(offset);
                    return true;
                } else if (this.prefetch) {
                    dataSource.prefetch(pageSkip, pageSize, function () {
                        buffer.offset = offset;
                        buffer._recalculate();
                        buffer._goToRange(offset, true);
                    });
                    return false;
                }

                return true;
            },

            syncDataSource: function () {
                var offset = this.offset;
                this.offset = null;
                this.range(offset);
            },

            destroy: function () {
                this.unbind();
            },

            _prefetch: function () {
                var buffer = this,
                pageSize = this.pageSize,
                prefetchOffset = this.skip + pageSize,
                dataSource = this.dataSource;

                if (!dataSource.inRange(prefetchOffset, pageSize) && !this._prefetching && this.prefetch) {
                    this._prefetching = true;
                    this.trigger("prefetching", { skip: prefetchOffset, take: pageSize });

                    dataSource.prefetch(prefetchOffset, pageSize, function () {
                        buffer._prefetching = false;
                        buffer.trigger("prefetched", { skip: prefetchOffset, take: pageSize });
                    });
                }
            },

            _goToRange: function (offset, expanding) {
                if (this.offset !== offset) {
                    return;
                }

                this.dataOffset = offset;
                this._expanding = expanding;
                this.dataSource.range(offset, this.pageSize);
                this.dataSource.enableRequestsInProgress();
            },

            _reset: function () {
                this._syncPending = true;
            },

            _change: function () {
                var dataSource = this.dataSource;

                this.length = this.useRanges ? dataSource.lastRange().end : dataSource.view().length;

                if (this._syncPending) {
                    this._syncWithDataSource();
                    this._recalculate();
                    this._syncPending = false;
                    this.trigger("reset", { offset: this.offset });
                }

                this.trigger("resize");

                if (this._expanding) {
                    this.trigger("expand");
                }

                delete this._expanding;
            },

            _syncWithDataSource: function () {
                var dataSource = this.dataSource;

                this._firstItemUid = dataSource.firstItemUid();
                this.dataOffset = this.offset = dataSource.skip() || 0;
                this.pageSize = dataSource.pageSize();
                this.useRanges = dataSource.options.serverPaging;
            },

            _recalculate: function () {
                var pageSize = this.pageSize,
                offset = this.offset,
                viewSize = this.viewSize,
                skip = Math.ceil(offset / pageSize) * pageSize;

                this.skip = skip;
                this.midPageThreshold = skip + pageSize - 1;
                this.nextPageThreshold = skip + viewSize - 1;
                this.prefetchThreshold = skip + Math.floor(pageSize / 3 * 2);
                this.pullBackThreshold = this.offset - 1;

                this.nextMidRange = skip + pageSize - viewSize;
                this.nextFullRange = skip;
                this.previousMidRange = offset - viewSize;
                this.previousFullRange = skip - pageSize;
            }
        });

        var BatchBuffer = kendo.Observable.extend({
            init: function (dataSource, batchSize) {
                var batchBuffer = this;

                kendo.Observable.fn.init.call(batchBuffer);

                this.dataSource = dataSource;
                this.batchSize = batchSize;
                this._total = 0;

                this.buffer = new Buffer(dataSource, batchSize * 3);

                this.buffer.bind({
                    "endreached": function (e) {
                        batchBuffer.trigger("endreached", { index: e.index });
                    },
                    "prefetching": function (e) {
                        batchBuffer.trigger("prefetching", { skip: e.skip, take: e.take });
                    },
                    "prefetched": function (e) {
                        batchBuffer.trigger("prefetched", { skip: e.skip, take: e.take });
                    },
                    "reset": function () {
                        batchBuffer._total = 0;
                        batchBuffer.trigger("reset");
                    },
                    "resize": function () {
                        batchBuffer._total = Math.ceil(this.length / batchBuffer.batchSize);
                        batchBuffer.trigger("resize", { total: batchBuffer.total(), offset: this.offset });
                    }
                });
            },

            syncDataSource: function () {
                this.buffer.syncDataSource();
            },

            at: function (index) {
                var buffer = this.buffer,
                skip = index * this.batchSize,
                take = this.batchSize,
                view = [],
                item;

                if (buffer.offset > skip) {
                    buffer.at(buffer.offset - 1);
                }

                for (var i = 0; i < take; i++) {
                    item = buffer.at(skip + i);

                    if (item === undefined) {
                        break;
                    }

                    view.push(item);
                }

                return view;
            },

            total: function () {
                return this._total;
            },

            destroy: function () {
                this.buffer.destroy();
                this.unbind();
            }
        });

        extend(true, kendo.data, {
            readers: {
                json: DataReader
            },
            Query: Query,
            DataSource: DataSource,
            HierarchicalDataSource: HierarchicalDataSource,
            Node: Node,
            ObservableObject: ObservableObject,
            ObservableArray: ObservableArray,
            LazyObservableArray: LazyObservableArray,
            LocalTransport: LocalTransport,
            RemoteTransport: RemoteTransport,
            Cache: Cache,
            DataReader: DataReader,
            Model: Model,
            Buffer: Buffer,
            BatchBuffer: BatchBuffer
        });
    })(window.kendo.jQuery);





    /*jshint eqnull: true */
    (function ($, undefined) {
        var kendo = window.kendo,
        browser = kendo.support.browser,
        Observable = kendo.Observable,
        ObservableObject = kendo.data.ObservableObject,
        ObservableArray = kendo.data.ObservableArray,
        toString = {}.toString,
        binders = {},
        slice = Array.prototype.slice,
        Class = kendo.Class,
        innerText,
        proxy = $.proxy,
        VALUE = "value",
        SOURCE = "source",
        EVENTS = "events",
        CHECKED = "checked",
        deleteExpando = true,
        CHANGE = "change";

        (function () {
            var a = document.createElement("a");

            if (a.innerText !== undefined) {
                innerText = "innerText";
            } else if (a.textContent !== undefined) {
                innerText = "textContent";
            }

            try {
                delete a.test;
            } catch (e) {
                deleteExpando = false;
            }
        })();

        var Binding = Observable.extend({
            init: function (parents, path) {
                var that = this;

                Observable.fn.init.call(that);

                that.source = parents[0];
                that.parents = parents;
                that.path = path;
                that.dependencies = {};
                that.dependencies[path] = true;
                that.observable = that.source instanceof Observable;

                that._access = function (e) {
                    that.dependencies[e.field] = true;
                };

                if (that.observable) {
                    that._change = function (e) {
                        that.change(e);
                    };

                    that.source.bind(CHANGE, that._change);
                }
            },

            _parents: function () {
                var parents = this.parents;
                var value = this.get();

                if (value && typeof value.parent == "function") {
                    var parent = value.parent();

                    if ($.inArray(parent, parents) < 0) {
                        parents = [parent].concat(parents);
                    }
                }

                return parents;
            },

            change: function (e) {
                var dependency,
                ch,
                field = e.field,
                that = this;

                if (that.path === "this") {
                    that.trigger(CHANGE, e);
                } else {
                    for (dependency in that.dependencies) {
                        if (dependency.indexOf(field) === 0) {
                            ch = dependency.charAt(field.length);

                            if (!ch || ch === "." || ch === "[") {
                                that.trigger(CHANGE, e);
                                break;
                            }
                        }
                    }
                }
            },

            start: function (source) {
                source.bind("get", this._access);
            },

            stop: function (source) {
                source.unbind("get", this._access);
            },

            get: function () {

                var that = this,
                source = that.source,
                index = 0,
                path = that.path,
                result = source;

                if (!that.observable) {
                    return result;
                }

                that.start(that.source);

                result = source.get(path);

                // Traverse the observable hierarchy if the binding is not resolved at the current level.
                while (result === undefined && source) {

                    source = that.parents[++index];

                    if (source instanceof ObservableObject) {
                        result = source.get(path);
                    }
                }

                // second pass try to get the parent from the object hierarchy
                if (result === undefined) {
                    source = that.source; //get the initial source

                    while (result === undefined && source) {
                        source = source.parent();

                        if (source instanceof ObservableObject) {
                            result = source.get(path);
                        }
                    }
                }

                // If the result is a function - invoke it
                if (typeof result === "function") {
                    index = path.lastIndexOf(".");

                    // If the function is a member of a nested observable object make that nested observable the context (this) of the function
                    if (index > 0) {
                        source = source.get(path.substring(0, index));
                    }

                    // Invoke the function
                    that.start(source);

                    if (source !== that.source) {
                        result = result.call(source, that.source);
                    } else {
                        result = result.call(source);
                    }

                    that.stop(source);
                }

                // If the binding is resolved by a parent object
                if (source && source !== that.source) {

                    that.currentSource = source; // save parent object

                    // Listen for changes in the parent object
                    source.unbind(CHANGE, that._change)
                      .bind(CHANGE, that._change);
                }

                that.stop(that.source);

                return result;
            },

            set: function (value) {
                var source = this.currentSource || this.source;

                var field = kendo.getter(this.path)(source);

                if (typeof field === "function") {
                    if (source !== this.source) {
                        field.call(source, this.source, value);
                    } else {
                        field.call(source, value);
                    }
                } else {
                    source.set(this.path, value);
                }
            },

            destroy: function () {
                if (this.observable) {
                    this.source.unbind(CHANGE, this._change);
                    if (this.currentSource) {
                        this.currentSource.unbind(CHANGE, this._change);
                    }
                }

                this.unbind();
            }
        });

        var EventBinding = Binding.extend({
            get: function () {
                var source = this.source,
                path = this.path,
                index = 0,
                handler;

                handler = source.get(path);

                while (!handler && source) {
                    source = this.parents[++index];

                    if (source instanceof ObservableObject) {
                        handler = source.get(path);
                    }
                }

                return proxy(handler, source);
            }
        });

        var TemplateBinding = Binding.extend({
            init: function (source, path, template) {
                var that = this;

                Binding.fn.init.call(that, source, path);

                that.template = template;
            },

            render: function (value) {
                var html;

                this.start(this.source);

                html = kendo.render(this.template, value);

                this.stop(this.source);

                return html;
            }
        });

        var Binder = Class.extend({
            init: function (element, bindings, options) {
                this.element = element;
                this.bindings = bindings;
                this.options = options;
            },

            bind: function (binding, attribute) {
                var that = this;

                binding = attribute ? binding[attribute] : binding;

                binding.bind(CHANGE, function (e) {
                    that.refresh(attribute || e);
                });

                that.refresh(attribute);
            },

            destroy: function () {
            }
        });

        binders.attr = Binder.extend({
            refresh: function (key) {
                this.element.setAttribute(key, this.bindings.attr[key].get());
            }
        });

        binders.style = Binder.extend({
            refresh: function (key) {
                this.element.style[key] = this.bindings.style[key].get() || "";
            }
        });

        binders.enabled = Binder.extend({
            refresh: function () {
                if (this.bindings.enabled.get()) {
                    this.element.removeAttribute("disabled");
                } else {
                    this.element.setAttribute("disabled", "disabled");
                }
            }
        });

        binders.readonly = Binder.extend({
            refresh: function () {
                if (this.bindings.readonly.get()) {
                    this.element.setAttribute("readonly", "readonly");
                } else {
                    this.element.removeAttribute("readonly");
                }
            }
        });

        binders.disabled = Binder.extend({
            refresh: function () {
                if (this.bindings.disabled.get()) {
                    this.element.setAttribute("disabled", "disabled");
                } else {
                    this.element.removeAttribute("disabled");
                }
            }
        });

        binders.events = Binder.extend({
            init: function (element, bindings, options) {
                Binder.fn.init.call(this, element, bindings, options);
                this.handlers = {};
            },

            refresh: function (key) {
                var element = $(this.element),
                binding = this.bindings.events[key],
                handler = this.handlers[key];

                if (handler) {
                    element.off(key, handler);
                }

                handler = this.handlers[key] = binding.get();

                element.on(key, binding.source, handler);
            },

            destroy: function () {
                var element = $(this.element),
                handler;

                for (handler in this.handlers) {
                    element.off(handler, this.handlers[handler]);
                }
            }
        });

        binders.text = Binder.extend({
            refresh: function () {
                var text = this.bindings.text.get();

                if (text == null) {
                    text = "";
                }

                this.element[innerText] = text;
            }
        });

        binders.visible = Binder.extend({
            refresh: function () {
                if (this.bindings.visible.get()) {
                    this.element.style.display = "";
                } else {
                    this.element.style.display = "none";
                }
            }
        });

        binders.invisible = Binder.extend({
            refresh: function () {
                if (!this.bindings.invisible.get()) {
                    this.element.style.display = "";
                } else {
                    this.element.style.display = "none";
                }
            }
        });

        binders.html = Binder.extend({
            refresh: function () {
                this.element.innerHTML = this.bindings.html.get();
            }
        });

        binders.value = Binder.extend({
            init: function (element, bindings, options) {
                Binder.fn.init.call(this, element, bindings, options);

                this._change = proxy(this.change, this);
                this.eventName = options.valueUpdate || CHANGE;

                $(this.element).on(this.eventName, this._change);

                this._initChange = false;
            },

            change: function () {
                this._initChange = this.eventName != CHANGE;

                var value = this.element.value;

                var type = this.element.type;

                if (type == "date") {
                    value = kendo.parseDate(value, "yyyy-MM-dd");
                } else if (type == "datetime-local") {
                    value = kendo.parseDate(value, ["yyyy-MM-ddTHH:mm:ss", "yyyy-MM-ddTHH:mm"]);
                } else if (type == "number") {
                    value = kendo.parseFloat(value);
                }

                this.bindings[VALUE].set(value);

                this._initChange = false;
            },

            refresh: function () {
                if (!this._initChange) {
                    var value = this.bindings[VALUE].get();

                    if (value == null) {
                        value = "";
                    }

                    var type = this.element.type;

                    if (type == "date") {
                        value = kendo.toString(value, "yyyy-MM-dd");
                    } else if (type == "datetime-local") {
                        value = kendo.toString(value, "yyyy-MM-ddTHH:mm:ss");
                    }

                    this.element.value = value;
                }

                this._initChange = false;
            },

            destroy: function () {
                $(this.element).off(this.eventName, this._change);
            }
        });

        binders.source = Binder.extend({
            init: function (element, bindings, options) {
                Binder.fn.init.call(this, element, bindings, options);

                var source = this.bindings.source.get();

                if (source instanceof kendo.data.DataSource && options.autoBind !== false) {
                    source.fetch();
                }
            },

            refresh: function (e) {
                var that = this,
                source = that.bindings.source.get();

                if (source instanceof ObservableArray || source instanceof kendo.data.DataSource) {
                    e = e || {};

                    if (e.action == "add") {
                        that.add(e.index, e.items);
                    } else if (e.action == "remove") {
                        that.remove(e.index, e.items);
                    } else if (e.action != "itemchange") {
                        that.render();
                    }
                } else {
                    that.render();
                }
            },

            container: function () {
                var element = this.element;

                if (element.nodeName.toLowerCase() == "table") {
                    if (!element.tBodies[0]) {
                        element.appendChild(document.createElement("tbody"));
                    }
                    element = element.tBodies[0];
                }

                return element;
            },

            template: function () {
                var options = this.options,
                template = options.template,
                nodeName = this.container().nodeName.toLowerCase();

                if (!template) {
                    if (nodeName == "select") {
                        if (options.valueField || options.textField) {
                            template = kendo.format('<option value="#:{0}#">#:{1}#</option>',
                            options.valueField || options.textField, options.textField || options.valueField);
                        } else {
                            template = "<option>#:data#</option>";
                        }
                    } else if (nodeName == "tbody") {
                        template = "<tr><td>#:data#</td></tr>";
                    } else if (nodeName == "ul" || nodeName == "ol") {
                        template = "<li>#:data#</li>";
                    } else {
                        template = "#:data#";
                    }

                    template = kendo.template(template);
                }

                return template;
            },

            add: function (index, items) {
                var element = this.container(),
                parents,
                idx,
                length,
                child,
                clone = element.cloneNode(false),
                reference = element.children[index];

                $(clone).html(kendo.render(this.template(), items));

                if (clone.children.length) {
                    parents = this.bindings.source._parents();

                    for (idx = 0, length = items.length; idx < length; idx++) {
                        child = clone.children[0];
                        element.insertBefore(child, reference || null);
                        bindElement(child, items[idx], this.options.roles, [items[idx]].concat(parents));
                    }
                }
            },

            remove: function (index, items) {
                var idx, element = this.container();

                for (idx = 0; idx < items.length; idx++) {
                    var child = element.children[index];
                    unbindElementTree(child);
                    element.removeChild(child);
                }
            },

            render: function () {
                var source = this.bindings.source.get(),
                parents,
                idx,
                length,
                element = this.container(),
                template = this.template();

                if (source instanceof kendo.data.DataSource) {
                    source = source.view();
                }

                if (!(source instanceof ObservableArray) && toString.call(source) !== "[object Array]") {
                    source = [source];
                }

                if (this.bindings.template) {
                    unbindElementChildren(element);

                    $(element).html(this.bindings.template.render(source));

                    if (element.children.length) {
                        parents = this.bindings.source._parents();

                        for (idx = 0, length = source.length; idx < length; idx++) {
                            bindElement(element.children[idx], source[idx], this.options.roles, [source[idx]].concat(parents));
                        }
                    }
                }
                else {
                    $(element).html(kendo.render(template, source));
                }
            }
        });

        binders.input = {
            checked: Binder.extend({
                init: function (element, bindings, options) {
                    Binder.fn.init.call(this, element, bindings, options);
                    this._change = proxy(this.change, this);

                    $(this.element).change(this._change);
                },
                change: function () {
                    var element = this.element;
                    var value = this.value();

                    if (element.type == "radio") {
                        this.bindings[CHECKED].set(value);
                    } else if (element.type == "checkbox") {
                        var source = this.bindings[CHECKED].get();
                        var index;

                        if (source instanceof ObservableArray) {
                            value = this.element.value;

                            if (value !== "on" && value !== "off") {
                                index = source.indexOf(value);
                                if (index > -1) {
                                    source.splice(index, 1);
                                } else {
                                    source.push(value);
                                }
                            }
                        } else {
                            this.bindings[CHECKED].set(value);
                        }
                    }
                },

                refresh: function () {
                    var value = this.bindings[CHECKED].get(),
                    source = value,
                    element = this.element;

                    if (element.type == "checkbox") {
                        if (source instanceof ObservableArray) {
                            value = this.element.value;
                            if (source.indexOf(value) >= 0) {
                                value = true;
                            }
                        }

                        element.checked = value === true;
                    } else if (element.type == "radio" && value != null) {
                        if (element.value === value.toString()) {
                            element.checked = true;
                        }
                    }
                },

                value: function () {
                    var element = this.element,
                    value = element.value;

                    if (element.type == "checkbox") {
                        value = element.checked;
                    }

                    return value;
                },
                destroy: function () {
                    $(this.element).off(CHANGE, this._change);
                }
            })
        };

        binders.select = {
            value: Binder.extend({
                init: function (target, bindings, options) {
                    Binder.fn.init.call(this, target, bindings, options);

                    this._change = proxy(this.change, this);
                    $(this.element).change(this._change);
                },

                change: function () {
                    var values = [],
                    element = this.element,
                    source,
                    field = this.options.valueField || this.options.textField,
                    valuePrimitive = this.options.valuePrimitive,
                    option,
                    valueIndex,
                    value,
                    idx,
                    length;

                    for (idx = 0, length = element.options.length; idx < length; idx++) {
                        option = element.options[idx];

                        if (option.selected) {
                            value = option.attributes.value;

                            if (value && value.specified) {
                                value = option.value;
                            } else {
                                value = option.text;
                            }

                            values.push(value);
                        }
                    }

                    if (field) {
                        source = this.bindings.source.get();
                        if (source instanceof kendo.data.DataSource) {
                            source = source.view();
                        }

                        for (valueIndex = 0; valueIndex < values.length; valueIndex++) {
                            for (idx = 0, length = source.length; idx < length; idx++) {
                                if (source[idx].get(field) == values[valueIndex]) {
                                    values[valueIndex] = source[idx];
                                    break;
                                }
                            }
                        }
                    }

                    value = this.bindings[VALUE].get();
                    if (value instanceof ObservableArray) {
                        value.splice.apply(value, [0, value.length].concat(values));
                    } else if (!valuePrimitive && (value instanceof ObservableObject || value === null || value === undefined || !field)) {
                        this.bindings[VALUE].set(values[0]);
                    } else {
                        this.bindings[VALUE].set(values[0].get(field));
                    }
                },
                refresh: function () {
                    var optionIndex,
                    element = this.element,
                    options = element.options,
                    value = this.bindings[VALUE].get(),
                    values = value,
                    field = this.options.valueField || this.options.textField,
                    found = false,
                    optionValue;

                    if (!(values instanceof ObservableArray)) {
                        values = new ObservableArray([value]);
                    }

                    element.selectedIndex = -1;

                    for (var valueIndex = 0; valueIndex < values.length; valueIndex++) {
                        value = values[valueIndex];

                        if (field && value instanceof ObservableObject) {
                            value = value.get(field);
                        }

                        for (optionIndex = 0; optionIndex < options.length; optionIndex++) {
                            optionValue = options[optionIndex].value;

                            if (optionValue === "" && value !== "") {
                                optionValue = options[optionIndex].text;
                            }

                            if (optionValue == value) {
                                options[optionIndex].selected = true;
                                found = true;
                            }
                        }
                    }
                },
                destroy: function () {
                    $(this.element).off(CHANGE, this._change);
                }
            })
        };

        function dataSourceBinding(bindingName, fieldName, setter) {
            return Binder.extend({
                init: function (widget, bindings, options) {
                    var that = this;

                    Binder.fn.init.call(that, widget.element[0], bindings, options);

                    that.widget = widget;
                    that._dataBinding = proxy(that.dataBinding, that);
                    that._dataBound = proxy(that.dataBound, that);
                    that._itemChange = proxy(that.itemChange, that);
                },

                itemChange: function (e) {
                    bindElement(e.item[0], e.data, this._ns(e.ns), [e.data].concat(this.bindings[bindingName]._parents()));
                },

                dataBinding: function (e) {
                    var idx,
                    length,
                    widget = this.widget,
                    items = e.removedItems || widget.items();

                    for (idx = 0, length = items.length; idx < length; idx++) {
                        unbindElementTree(items[idx]);
                    }
                },

                _ns: function (ns) {
                    ns = ns || kendo.ui;
                    var all = [kendo.ui, kendo.dataviz.ui, kendo.mobile.ui];
                    all.splice($.inArray(ns, all), 1);
                    all.unshift(ns);

                    return kendo.rolesFromNamespaces(all);
                },

                dataBound: function (e) {
                    var idx,
                    length,
                    widget = this.widget,
                    items = e.addedItems || widget.items(),
                    dataSource = widget[fieldName],
                    view,
                    parents,
                    groups = dataSource.group() || [];

                    if (items.length) {
                        view = e.addedDataItems || dataSource.flatView();
                        parents = this.bindings[bindingName]._parents();

                        for (idx = 0, length = view.length; idx < length; idx++) {
                            bindElement(items[idx], view[idx], this._ns(e.ns), [view[idx]].concat(parents));
                        }
                    }
                },

                refresh: function (e) {
                    var that = this,
                    source,
                    widget = that.widget;

                    e = e || {};

                    if (!e.action) {
                        that.destroy();

                        widget.bind("dataBinding", that._dataBinding);
                        widget.bind("dataBound", that._dataBound);
                        widget.bind("itemChange", that._itemChange);

                        source = that.bindings[bindingName].get();

                        if (widget[fieldName] instanceof kendo.data.DataSource && widget[fieldName] != source) {
                            if (source instanceof kendo.data.DataSource) {
                                widget[setter](source);
                            } else if (source && source._dataSource) {
                                widget[setter](source._dataSource);
                            } else {
                                widget[fieldName].data(source);
                            }
                        }
                    }
                },

                destroy: function () {
                    var widget = this.widget;

                    widget.unbind("dataBinding", this._dataBinding);
                    widget.unbind("dataBound", this._dataBound);
                    widget.unbind("itemChange", this._itemChange);
                }
            });
        }

        binders.widget = {
            events: Binder.extend({
                init: function (widget, bindings, options) {
                    Binder.fn.init.call(this, widget.element[0], bindings, options);
                    this.widget = widget;
                    this.handlers = {};
                },

                refresh: function (key) {
                    var binding = this.bindings.events[key],
                    handler = this.handlers[key];

                    if (handler) {
                        this.widget.unbind(key, handler);
                    }

                    handler = binding.get();

                    this.handlers[key] = function (e) {
                        e.data = binding.source;

                        handler(e);

                        if (e.data === binding.source) {
                            delete e.data;
                        }
                    };

                    this.widget.bind(key, this.handlers[key]);
                },

                destroy: function () {
                    var handler;

                    for (handler in this.handlers) {
                        this.widget.unbind(handler, this.handlers[handler]);
                    }
                }
            }),

            checked: Binder.extend({
                init: function (widget, bindings, options) {
                    Binder.fn.init.call(this, widget.element[0], bindings, options);

                    this.widget = widget;
                    this._change = proxy(this.change, this);
                    this.widget.bind(CHANGE, this._change);
                },
                change: function () {
                    this.bindings[CHECKED].set(this.value());
                },

                refresh: function () {
                    this.widget.check(this.bindings[CHECKED].get() === true);
                },

                value: function () {
                    var element = this.element,
                    value = element.value;

                    if (value == "on" || value == "off") {
                        value = element.checked;
                    }

                    return value;
                },

                destroy: function () {
                    this.widget.unbind(CHANGE, this._change);
                }
            }),

            visible: Binder.extend({
                init: function (widget, bindings, options) {
                    Binder.fn.init.call(this, widget.element[0], bindings, options);

                    this.widget = widget;
                },

                refresh: function () {
                    var visible = this.bindings.visible.get();
                    this.widget.wrapper[0].style.display = visible ? "" : "none";
                }
            }),

            invisible: Binder.extend({
                init: function (widget, bindings, options) {
                    Binder.fn.init.call(this, widget.element[0], bindings, options);

                    this.widget = widget;
                },

                refresh: function () {
                    var invisible = this.bindings.invisible.get();
                    this.widget.wrapper[0].style.display = invisible ? "none" : "";
                }
            }),

            enabled: Binder.extend({
                init: function (widget, bindings, options) {
                    Binder.fn.init.call(this, widget.element[0], bindings, options);

                    this.widget = widget;
                },

                refresh: function () {
                    if (this.widget.enable) {
                        this.widget.enable(this.bindings.enabled.get());
                    }
                }
            }),

            disabled: Binder.extend({
                init: function (widget, bindings, options) {
                    Binder.fn.init.call(this, widget.element[0], bindings, options);

                    this.widget = widget;
                },

                refresh: function () {
                    if (this.widget.enable) {
                        this.widget.enable(!this.bindings.disabled.get());
                    }
                }
            }),

            source: dataSourceBinding("source", "dataSource", "setDataSource"),

            value: Binder.extend({
                init: function (widget, bindings, options) {
                    Binder.fn.init.call(this, widget.element[0], bindings, options);

                    this.widget = widget;
                    this._change = $.proxy(this.change, this);
                    this.widget.first(CHANGE, this._change);

                    var value = this.bindings.value.get();

                    this._valueIsObservableObject = !options.valuePrimitive && (value == null || value instanceof ObservableObject);
                    this._valueIsObservableArray = value instanceof ObservableArray;
                    this._initChange = false;
                },

                change: function () {
                    var value = this.widget.value(),
                    field = this.options.dataValueField || this.options.dataTextField,
                    isArray = toString.call(value) === "[object Array]",
                    isObservableObject = this._valueIsObservableObject,
                    valueIndex, valueLength, values = [],
                    sourceItem, sourceValue,
                    idx, length, source;

                    this._initChange = true;

                    if (field) {

                        if (this.bindings.source) {
                            source = this.bindings.source.get();
                        }

                        if (value === "" && (isObservableObject || this.options.valuePrimitive)) {
                            value = null;
                        } else {
                            if (!source || source instanceof kendo.data.DataSource) {
                                source = this.widget.dataSource.view();
                            }

                            if (isArray) {
                                valueLength = value.length;
                                values = value.slice(0);
                            }

                            for (idx = 0, length = source.length; idx < length; idx++) {
                                sourceItem = source[idx];
                                sourceValue = sourceItem.get(field);

                                if (isArray) {
                                    for (valueIndex = 0; valueIndex < valueLength; valueIndex++) {
                                        if (sourceValue == values[valueIndex]) {
                                            values[valueIndex] = sourceItem;
                                            break;
                                        }
                                    }
                                } else if (sourceValue == value) {
                                    value = isObservableObject ? sourceItem : sourceValue;
                                    break;
                                }
                            }

                            if (values[0]) {
                                if (this._valueIsObservableArray) {
                                    value = values;
                                } else if (isObservableObject || !field) {
                                    value = values[0];
                                } else {
                                    value = values[0].get(field);
                                }
                            }
                        }
                    }

                    this.bindings.value.set(value);
                    this._initChange = false;
                },

                refresh: function () {

                    if (!this._initChange) {
                        var field = this.options.dataValueField || this.options.dataTextField,
                        value = this.bindings.value.get(),
                              idx = 0, length,
                              values = [];

                        if (value === undefined) {
                            value = null;
                        }

                        if (field) {
                            if (value instanceof ObservableArray) {
                                for (length = value.length; idx < length; idx++) {
                                    values[idx] = value[idx].get(field);
                                }
                                value = values;
                            } else if (value instanceof ObservableObject) {
                                value = value.get(field);
                            }
                        }
                        this.widget.value(value);
                    }

                    this._initChange = false;
                },

                destroy: function () {
                    this.widget.unbind(CHANGE, this._change);
                }
            }),

            gantt: {
                dependencies: dataSourceBinding("dependencies", "dependencies", "setDependenciesDataSource")
            },

            multiselect: {
                value: Binder.extend({
                    init: function (widget, bindings, options) {
                        Binder.fn.init.call(this, widget.element[0], bindings, options);

                        this.widget = widget;
                        this._change = $.proxy(this.change, this);
                        this.widget.first(CHANGE, this._change);
                        this._initChange = false;
                    },

                    change: function () {
                        var that = this,
                        oldValues = that.bindings[VALUE].get(),
                        valuePrimitive = that.options.valuePrimitive,
                        newValues = valuePrimitive ? that.widget.value() : that.widget.dataItems();

                        var field = this.options.dataValueField || this.options.dataTextField;

                        newValues = newValues.slice(0);

                        that._initChange = true;

                        if (oldValues instanceof ObservableArray) {
                            var remove = [];

                            var newLength = newValues.length;

                            var i = 0, j = 0;
                            var old = oldValues[i];
                            var same = false;
                            var removeIndex;
                            var newValue;
                            var found;

                            while (old) {
                                found = false;
                                for (j = 0; j < newLength; j++) {
                                    if (valuePrimitive) {
                                        same = newValues[j] == old;
                                    } else {
                                        newValue = newValues[j];

                                        newValue = newValue.get ? newValue.get(field) : newValue;
                                        same = newValue == (old.get ? old.get(field) : old);
                                    }

                                    if (same) {
                                        newValues.splice(j, 1);
                                        newLength -= 1;
                                        found = true;
                                        break;
                                    }
                                }

                                if (!found) {
                                    remove.push(old);
                                    arraySplice(oldValues, i, 1);
                                    removeIndex = i;
                                } else {
                                    i += 1;
                                }

                                old = oldValues[i];
                            }

                            arraySplice(oldValues, oldValues.length, 0, newValues);

                            if (remove.length) {
                                oldValues.trigger("change", {
                                    action: "remove",
                                    items: remove,
                                    index: removeIndex
                                });
                            }

                            if (newValues.length) {
                                oldValues.trigger("change", {
                                    action: "add",
                                    items: newValues,
                                    index: oldValues.length - 1
                                });
                            }
                        } else {
                            that.bindings[VALUE].set(newValues);
                        }

                        that._initChange = false;
                    },

                    refresh: function () {
                        if (!this._initChange) {
                            var field = this.options.dataValueField || this.options.dataTextField,
                            value = this.bindings.value.get(),
                            idx = 0, length,
                            values = [],
                            selectedValue;

                            if (value === undefined) {
                                value = null;
                            }

                            if (field) {
                                if (value instanceof ObservableArray) {
                                    for (length = value.length; idx < length; idx++) {
                                        selectedValue = value[idx];
                                        values[idx] = selectedValue.get ? selectedValue.get(field) : selectedValue;
                                    }
                                    value = values;
                                } else if (value instanceof ObservableObject) {
                                    value = value.get(field);
                                }
                            }

                            this.widget.value(value);
                        }
                    },

                    destroy: function () {
                        this.widget.unbind(CHANGE, this._change);
                    }

                })
            },
            scheduler: {
                source: dataSourceBinding("source", "dataSource", "setDataSource").extend({
                    dataBound: function (e) {
                        var idx;
                        var length;
                        var widget = this.widget;
                        var view = widget.view();
                        var items = e.addedItems || widget.items();
                        var dataSource = widget.dataSource;
                        var groups = dataSource.group() || [];
                        var data, parents;

                        if (items.length) {
                            data = e.addedDataItems || dataSource.expand(view.startDate(), view.endDate());
                            parents = this.bindings.source._parents();

                            for (idx = 0, length = data.length; idx < length; idx++) {
                                bindElement(items[idx], data[idx], this._ns(e.ns), [data[idx]].concat(parents));
                            }
                        }
                    }
                })
            }
        };

        var arraySplice = function (arr, idx, remove, add) {
            add = add || [];
            remove = remove || 0;

            var addLength = add.length;
            var oldLength = arr.length;

            var shifted = [].slice.call(arr, idx + remove);
            var shiftedLength = shifted.length;
            var index;

            if (addLength) {
                addLength = idx + addLength;
                index = 0;

                for (; idx < addLength; idx++) {
                    arr[idx] = add[index];
                    index++;
                }

                arr.length = addLength;
            } else if (remove) {
                arr.length = idx;

                remove += idx;
                while (idx < remove) {
                    delete arr[--remove];
                }
            }

            if (shiftedLength) {
                shiftedLength = idx + shiftedLength;
                index = 0;

                for (; idx < shiftedLength; idx++) {
                    arr[idx] = shifted[index];
                    index++;
                }

                arr.length = shiftedLength;
            }

            idx = arr.length;

            while (idx < oldLength) {
                delete arr[idx];
                idx++;
            }
        };

        var BindingTarget = Class.extend({
            init: function (target, options) {
                this.target = target;
                this.options = options;
                this.toDestroy = [];
            },

            bind: function (bindings) {
                var nodeName = this.target.nodeName.toLowerCase(),
                key,
                hasValue,
                hasSource,
                hasEvents,
                specificBinders = binders[nodeName] || {};

                for (key in bindings) {
                    if (key == VALUE) {
                        hasValue = true;
                    } else if (key == SOURCE) {
                        hasSource = true;
                    } else if (key == EVENTS) {
                        hasEvents = true;
                    } else {
                        this.applyBinding(key, bindings, specificBinders);
                    }
                }

                if (hasSource) {
                    this.applyBinding(SOURCE, bindings, specificBinders);
                }

                if (hasValue) {
                    this.applyBinding(VALUE, bindings, specificBinders);
                }

                if (hasEvents) {
                    this.applyBinding(EVENTS, bindings, specificBinders);
                }
            },

            applyBinding: function (name, bindings, specificBinders) {
                var binder = specificBinders[name] || binders[name],
                toDestroy = this.toDestroy,
                attribute,
                binding = bindings[name];

                if (binder) {
                    binder = new binder(this.target, bindings, this.options);

                    toDestroy.push(binder);

                    if (binding instanceof Binding) {
                        binder.bind(binding);
                        toDestroy.push(binding);
                    } else {
                        for (attribute in binding) {
                            binder.bind(binding, attribute);
                            toDestroy.push(binding[attribute]);
                        }
                    }
                } else if (name !== "template") {
                    throw new Error("The " + name + " binding is not supported by the " + this.target.nodeName.toLowerCase() + " element");
                }
            },

            destroy: function () {
                var idx,
                length,
                toDestroy = this.toDestroy;

                for (idx = 0, length = toDestroy.length; idx < length; idx++) {
                    toDestroy[idx].destroy();
                }
            }
        });

        var WidgetBindingTarget = BindingTarget.extend({
            bind: function (bindings) {
                var that = this,
                binding,
                hasValue = false,
                hasSource = false,
                specificBinders = binders.widget[that.target.options.name.toLowerCase()] || {};

                for (binding in bindings) {
                    if (binding == VALUE) {
                        hasValue = true;
                    } else if (binding == SOURCE) {
                        hasSource = true;
                    } else {
                        that.applyBinding(binding, bindings, specificBinders);
                    }
                }

                if (hasSource) {
                    that.applyBinding(SOURCE, bindings, specificBinders);
                }

                if (hasValue) {
                    that.applyBinding(VALUE, bindings, specificBinders);
                }
            },

            applyBinding: function (name, bindings, specificBinders) {
                var binder = specificBinders[name] || binders.widget[name],
                toDestroy = this.toDestroy,
                attribute,
                binding = bindings[name];

                if (binder) {
                    binder = new binder(this.target, bindings, this.target.options);

                    toDestroy.push(binder);


                    if (binding instanceof Binding) {
                        binder.bind(binding);
                        toDestroy.push(binding);
                    } else {
                        for (attribute in binding) {
                            binder.bind(binding, attribute);
                            toDestroy.push(binding[attribute]);
                        }
                    }
                } else {
                    throw new Error("The " + name + " binding is not supported by the " + this.target.options.name + " widget");
                }
            }
        });

        function bindingTargetForRole(element, roles) {
            var widget = kendo.initWidget(element, {}, roles);

            if (widget) {
                return new WidgetBindingTarget(widget);
            }
        }

        var keyValueRegExp = /[A-Za-z0-9_\-]+:(\{([^}]*)\}|[^,}]+)/g,
        whiteSpaceRegExp = /\s/g;

        function parseBindings(bind) {
            var result = {},
            idx,
            length,
            token,
            colonIndex,
            key,
            value,
            tokens;

            tokens = bind.match(keyValueRegExp);

            for (idx = 0, length = tokens.length; idx < length; idx++) {
                token = tokens[idx];
                colonIndex = token.indexOf(":");

                key = token.substring(0, colonIndex);
                value = token.substring(colonIndex + 1);

                if (value.charAt(0) == "{") {
                    value = parseBindings(value);
                }

                result[key] = value;
            }

            return result;
        }

        function createBindings(bindings, source, type) {
            var binding,
            result = {};

            for (binding in bindings) {
                result[binding] = new type(source, bindings[binding]);
            }

            return result;
        }

        function bindElement(element, source, roles, parents) {
            var role = element.getAttribute("data-" + kendo.ns + "role"),
            idx,
            bind = element.getAttribute("data-" + kendo.ns + "bind"),
            children = element.children,
            childrenCopy = [],
            deep = true,
            bindings,
            options = {},
            target;

            parents = parents || [source];

            if (role || bind) {
                unbindElement(element);
            }

            if (role) {
                target = bindingTargetForRole(element, roles);
            }

            if (bind) {
                bind = parseBindings(bind.replace(whiteSpaceRegExp, ""));

                if (!target) {
                    options = kendo.parseOptions(element, { textField: "", valueField: "", template: "", valueUpdate: CHANGE, valuePrimitive: false, autoBind: true });
                    options.roles = roles;
                    target = new BindingTarget(element, options);
                }

                target.source = source;

                bindings = createBindings(bind, parents, Binding);

                if (options.template) {
                    bindings.template = new TemplateBinding(parents, "", options.template);
                }

                if (bindings.click) {
                    bind.events = bind.events || {};
                    bind.events.click = bind.click;
                    bindings.click.destroy();
                    delete bindings.click;
                }

                if (bindings.source) {
                    deep = false;
                }

                if (bind.attr) {
                    bindings.attr = createBindings(bind.attr, parents, Binding);
                }

                if (bind.style) {
                    bindings.style = createBindings(bind.style, parents, Binding);
                }

                if (bind.events) {
                    bindings.events = createBindings(bind.events, parents, EventBinding);
                }

                target.bind(bindings);
            }

            if (target) {
                element.kendoBindingTarget = target;
            }

            if (deep && children) {
                // https://github.com/telerik/kendo/issues/1240 for the weirdness.
                for (idx = 0; idx < children.length; idx++) {
                    childrenCopy[idx] = children[idx];
                }

                for (idx = 0; idx < childrenCopy.length; idx++) {
                    bindElement(childrenCopy[idx], source, roles, parents);
                }
            }
        }

        function bind(dom, object) {
            var idx,
            length,
            node,
            roles = kendo.rolesFromNamespaces([].slice.call(arguments, 2));

            object = kendo.observable(object);
            dom = $(dom);

            for (idx = 0, length = dom.length; idx < length; idx++) {
                node = dom[idx];
                if (node.nodeType === 1) {
                    bindElement(node, object, roles);
                }
            }
        }

        function unbindElement(element) {
            var bindingTarget = element.kendoBindingTarget;

            if (bindingTarget) {
                bindingTarget.destroy();

                if (deleteExpando) {
                    delete element.kendoBindingTarget;
                } else if (element.removeAttribute) {
                    element.removeAttribute("kendoBindingTarget");
                } else {
                    element.kendoBindingTarget = null;
                }
            }
        }

        function unbindElementTree(element) {
            unbindElement(element);

            unbindElementChildren(element);
        }

        function unbindElementChildren(element) {
            var children = element.children;

            if (children) {
                for (var idx = 0, length = children.length; idx < length; idx++) {
                    unbindElementTree(children[idx]);
                }
            }
        }

        function unbind(dom) {
            var idx, length;

            dom = $(dom);

            for (idx = 0, length = dom.length; idx < length; idx++) {
                unbindElementTree(dom[idx]);
            }
        }

        function notify(widget, namespace) {
            var element = widget.element,
            bindingTarget = element[0].kendoBindingTarget;

            if (bindingTarget) {
                bind(element, bindingTarget.source, namespace);
            }
        }

        kendo.unbind = unbind;
        kendo.bind = bind;
        kendo.data.binders = binders;
        kendo.data.Binder = Binder;
        kendo.notify = notify;

        kendo.observable = function (object) {
            if (!(object instanceof ObservableObject)) {
                object = new ObservableObject(object);
            }

            return object;
        };

        kendo.observableHierarchy = function (array) {
            var dataSource = kendo.data.HierarchicalDataSource.create(array);

            function recursiveRead(data) {
                var i, children;

                for (i = 0; i < data.length; i++) {
                    data[i]._initChildren();

                    children = data[i].children;

                    children.fetch();

                    data[i].items = children.data();

                    recursiveRead(data[i].items);
                }
            }

            dataSource.fetch();

            recursiveRead(dataSource.data());

            dataSource._data._dataSource = dataSource;

            return dataSource._data;
        };

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        fx = kendo.effects,
        each = $.each,
        extend = $.extend,
        proxy = $.proxy,
        support = kendo.support,
        browser = support.browser,
        transforms = support.transforms,
        transitions = support.transitions,
        scaleProperties = { scale: 0, scalex: 0, scaley: 0, scale3d: 0 },
        translateProperties = { translate: 0, translatex: 0, translatey: 0, translate3d: 0 },
        hasZoom = (typeof document.documentElement.style.zoom !== "undefined") && !transforms,
        matrix3dRegExp = /matrix3?d?\s*\(.*,\s*([\d\.\-]+)\w*?,\s*([\d\.\-]+)\w*?,\s*([\d\.\-]+)\w*?,\s*([\d\.\-]+)\w*?/i,
        cssParamsRegExp = /^(-?[\d\.\-]+)?[\w\s]*,?\s*(-?[\d\.\-]+)?[\w\s]*/i,
        translateXRegExp = /translatex?$/i,
        oldEffectsRegExp = /(zoom|fade|expand)(\w+)/,
        singleEffectRegExp = /(zoom|fade|expand)/,
        unitRegExp = /[xy]$/i,
        transformProps = ["perspective", "rotate", "rotatex", "rotatey", "rotatez", "rotate3d", "scale", "scalex", "scaley", "scalez", "scale3d", "skew", "skewx", "skewy", "translate", "translatex", "translatey", "translatez", "translate3d", "matrix", "matrix3d"],
        transform2d = ["rotate", "scale", "scalex", "scaley", "skew", "skewx", "skewy", "translate", "translatex", "translatey", "matrix"],
        transform2units = { "rotate": "deg", scale: "", skew: "px", translate: "px" },
        cssPrefix = transforms.css,
        round = Math.round,
        BLANK = "",
        PX = "px",
        NONE = "none",
        AUTO = "auto",
        WIDTH = "width",
        HEIGHT = "height",
        HIDDEN = "hidden",
        ORIGIN = "origin",
        ABORT_ID = "abortId",
        OVERFLOW = "overflow",
        TRANSLATE = "translate",
        POSITION = "position",
        COMPLETE_CALLBACK = "completeCallback",
        TRANSITION = cssPrefix + "transition",
        TRANSFORM = cssPrefix + "transform",
        BACKFACE = cssPrefix + "backface-visibility",
        PERSPECTIVE = cssPrefix + "perspective",
        DEFAULT_PERSPECTIVE = "1500px",
        TRANSFORM_PERSPECTIVE = "perspective(" + DEFAULT_PERSPECTIVE + ")",
        ios7 = support.mobileOS && support.mobileOS.majorVersion == 7,
        directions = {
            left: {
                reverse: "right",
                property: "left",
                transition: "translatex",
                vertical: false,
                modifier: -1
            },
            right: {
                reverse: "left",
                property: "left",
                transition: "translatex",
                vertical: false,
                modifier: 1
            },
            down: {
                reverse: "up",
                property: "top",
                transition: "translatey",
                vertical: true,
                modifier: 1
            },
            up: {
                reverse: "down",
                property: "top",
                transition: "translatey",
                vertical: true,
                modifier: -1
            },
            top: {
                reverse: "bottom"
            },
            bottom: {
                reverse: "top"
            },
            "in": {
                reverse: "out",
                modifier: -1
            },
            out: {
                reverse: "in",
                modifier: 1
            },

            vertical: {
                reverse: "vertical"
            },

            horizontal: {
                reverse: "horizontal"
            }
        };

        kendo.directions = directions;

        extend($.fn, {
            kendoStop: function (clearQueue, gotoEnd) {
                if (transitions) {
                    return fx.stopQueue(this, clearQueue || false, gotoEnd || false);
                } else {
                    return this.stop(clearQueue, gotoEnd);
                }
            }
        });

        /* jQuery support for all transform animations (FF 3.5/3.6, Opera 10.x, IE9 */

        if (transforms && !transitions) {
            each(transform2d, function (idx, value) {
                $.fn[value] = function (val) {
                    if (typeof val == "undefined") {
                        return animationProperty(this, value);
                    } else {
                        var that = $(this)[0],
                        transformValue = value + "(" + val + transform2units[value.replace(unitRegExp, "")] + ")";

                        if (that.style.cssText.indexOf(TRANSFORM) == -1) {
                            $(this).css(TRANSFORM, transformValue);
                        } else {
                            that.style.cssText = that.style.cssText.replace(new RegExp(value + "\\(.*?\\)", "i"), transformValue);
                        }
                    }
                    return this;
                };

                $.fx.step[value] = function (fx) {
                    $(fx.elem)[value](fx.now);
                };
            });

            var curProxy = $.fx.prototype.cur;
            $.fx.prototype.cur = function () {
                if (transform2d.indexOf(this.prop) != -1) {
                    return parseFloat($(this.elem)[this.prop]());
                }

                return curProxy.apply(this, arguments);
            };
        }

        kendo.toggleClass = function (element, classes, options, add) {
            if (classes) {
                classes = classes.split(" ");

                if (transitions) {
                    options = extend({
                        exclusive: "all",
                        duration: 400,
                        ease: "ease-out"
                    }, options);

                    element.css(TRANSITION, options.exclusive + " " + options.duration + "ms " + options.ease);
                    setTimeout(function () {
                        element.css(TRANSITION, "").css(HEIGHT);
                    }, options.duration); // TODO: this should fire a kendoAnimate session instead.
                }

                each(classes, function (idx, value) {
                    element.toggleClass(value, add);
                });
            }

            return element;
        };

        kendo.parseEffects = function (input, mirror) {
            var effects = {};

            if (typeof input === "string") {
                each(input.split(" "), function (idx, value) {
                    var redirectedEffect = !singleEffectRegExp.test(value),
                    resolved = value.replace(oldEffectsRegExp, function (match, $1, $2) {
                        return $1 + ":" + $2.toLowerCase();
                    }), // Support for old zoomIn/fadeOut style, now deprecated.
                    effect = resolved.split(":"),
                    direction = effect[1],
                    effectBody = {};

                    if (effect.length > 1) {
                        effectBody.direction = (mirror && redirectedEffect ? directions[direction].reverse : direction);
                    }

                    effects[effect[0]] = effectBody;
                });
            } else {
                each(input, function (idx) {
                    var direction = this.direction;

                    if (direction && mirror && !singleEffectRegExp.test(idx)) {
                        this.direction = directions[direction].reverse;
                    }

                    effects[idx] = this;
                });
            }

            return effects;
        };

        function parseInteger(value) {
            return parseInt(value, 10);
        }

        function parseCSS(element, property) {
            return parseInteger(element.css(property));
        }

        function keys(obj) {
            var acc = [];
            for (var propertyName in obj) {
                acc.push(propertyName);
            }
            return acc;
        }

        function strip3DTransforms(properties) {
            for (var key in properties) {
                if (transformProps.indexOf(key) != -1 && transform2d.indexOf(key) == -1) {
                    delete properties[key];
                }
            }

            return properties;
        }

        function normalizeCSS(element, properties) {
            var transformation = [], cssValues = {}, lowerKey, key, value, isTransformed;

            for (key in properties) {
                lowerKey = key.toLowerCase();
                isTransformed = transforms && transformProps.indexOf(lowerKey) != -1;

                if (!support.hasHW3D && isTransformed && transform2d.indexOf(lowerKey) == -1) {
                    delete properties[key];
                } else {
                    value = properties[key];

                    if (isTransformed) {
                        transformation.push(key + "(" + value + ")");
                    } else {
                        cssValues[key] = value;
                    }
                }
            }

            if (transformation.length) {
                cssValues[TRANSFORM] = transformation.join(" ");
            }

            return cssValues;
        }

        if (transitions) {
            extend(fx, {
                transition: function (element, properties, options) {
                    var css,
                    delay = 0,
                    oldKeys = element.data("keys") || [],
                    timeoutID;

                    options = extend({
                        duration: 200,
                        ease: "ease-out",
                        complete: null,
                        exclusive: "all"
                    },
                    options
                );

                    var stopTransitionCalled = false;

                    var stopTransition = function () {
                        if (!stopTransitionCalled) {
                            stopTransitionCalled = true;

                            if (timeoutID) {
                                clearTimeout(timeoutID);
                                timeoutID = null;
                            }

                            element
                        .removeData(ABORT_ID)
                        .dequeue()
                        .css(TRANSITION, "")
                        .css(TRANSITION);

                            options.complete.call(element);
                        }
                    };

                    options.duration = $.fx ? $.fx.speeds[options.duration] || options.duration : options.duration;

                    css = normalizeCSS(element, properties);

                    $.merge(oldKeys, keys(css));
                    element
                    .data("keys", $.unique(oldKeys))
                    .height();

                    element.css(TRANSITION, options.exclusive + " " + options.duration + "ms " + options.ease).css(TRANSITION);
                    element.css(css).css(TRANSFORM);

                    /**
                    * Use transitionEnd event for browsers who support it - but duplicate it with setTimeout, as the transitionEnd event will not be triggered if no CSS properties change.
                    * This should be cleaned up at some point (widget by widget), and refactored to widgets not relying on the complete callback if no transition occurs.
                    *
                    * For IE9 and below, resort to setTimeout.
                    */
                    if (transitions.event) {
                        element.one(transitions.event, stopTransition);
                        if (options.duration !== 0) {
                            delay = 500;
                        }
                    }

                    timeoutID = setTimeout(stopTransition, options.duration + delay);
                    element.data(ABORT_ID, timeoutID);
                    element.data(COMPLETE_CALLBACK, stopTransition);
                },

                stopQueue: function (element, clearQueue, gotoEnd) {
                    var cssValues,
                    taskKeys = element.data("keys"),
                    retainPosition = (!gotoEnd && taskKeys),
                    completeCallback = element.data(COMPLETE_CALLBACK);

                    if (retainPosition) {
                        cssValues = kendo.getComputedStyles(element[0], taskKeys);
                    }

                    if (completeCallback) {
                        completeCallback();
                    }

                    if (retainPosition) {
                        element.css(cssValues);
                    }

                    return element
                        .removeData("keys")
                        .stop(clearQueue);
                }
            });
        }

        function animationProperty(element, property) {
            if (transforms) {
                var transform = element.css(TRANSFORM);
                if (transform == NONE) {
                    return property == "scale" ? 1 : 0;
                }

                var match = transform.match(new RegExp(property + "\\s*\\(([\\d\\w\\.]+)")),
                computed = 0;

                if (match) {
                    computed = parseInteger(match[1]);
                } else {
                    match = transform.match(matrix3dRegExp) || [0, 0, 0, 0, 0];
                    property = property.toLowerCase();

                    if (translateXRegExp.test(property)) {
                        computed = parseFloat(match[3] / match[2]);
                    } else if (property == "translatey") {
                        computed = parseFloat(match[4] / match[2]);
                    } else if (property == "scale") {
                        computed = parseFloat(match[2]);
                    } else if (property == "rotate") {
                        computed = parseFloat(Math.atan2(match[2], match[1]));
                    }
                }

                return computed;
            } else {
                return parseFloat(element.css(property));
            }
        }

        var EffectSet = kendo.Class.extend({
            init: function (element, options) {
                var that = this;

                that.element = element;
                that.effects = [];
                that.options = options;
                that.restore = [];
            },

            run: function (effects) {
                var that = this,
                effect,
                idx, jdx,
                length = effects.length,
                element = that.element,
                options = that.options,
                deferred = $.Deferred(),
                start = {},
                end = {},
                target,
                children,
                childrenLength;

                that.effects = effects;

                deferred.then($.proxy(that, "complete"));

                element.data("animating", true);

                for (idx = 0; idx < length; idx++) {
                    effect = effects[idx];

                    effect.setReverse(options.reverse);
                    effect.setOptions(options);

                    that.addRestoreProperties(effect.restore);

                    effect.prepare(start, end);

                    children = effect.children();

                    for (jdx = 0, childrenLength = children.length; jdx < childrenLength; jdx++) {
                        children[jdx].duration(options.duration).run();
                    }
                }

                // legacy support for options.properties
                for (var effectName in options.effects) {
                    extend(end, options.effects[effectName].properties);
                }

                // Show the element initially
                if (!element.is(":visible")) {
                    extend(start, { display: element.data("olddisplay") || "block" });
                }

                if (transforms && !options.reset) {
                    target = element.data("targetTransform");

                    if (target) {
                        start = extend(target, start);
                    }
                }

                start = normalizeCSS(element, start);

                if (transforms && !transitions) {
                    start = strip3DTransforms(start);
                }

                element.css(start)
                   .css(TRANSFORM); // Nudge

                for (idx = 0; idx < length; idx++) {
                    effects[idx].setup();
                }

                if (options.init) {
                    options.init();
                }

                element.data("targetTransform", end);
                fx.animate(element, end, extend({}, options, { complete: deferred.resolve }));

                return deferred.promise();
            },

            stop: function () {
                $(this.element).kendoStop(true, true);
            },

            addRestoreProperties: function (restore) {
                var element = this.element,
                value,
                i = 0,
                length = restore.length;

                for (; i < length; i++) {
                    value = restore[i];

                    this.restore.push(value);

                    if (!element.data(value)) {
                        element.data(value, element.css(value));
                    }
                }
            },

            restoreCallback: function () {
                var element = this.element;

                for (var i = 0, length = this.restore.length; i < length; i++) {
                    var value = this.restore[i];
                    element.css(value, element.data(value));
                }
            },

            complete: function () {
                var that = this,
                idx = 0,
                element = that.element,
                options = that.options,
                effects = that.effects,
                length = effects.length;

                element
                .removeData("animating")
                .dequeue(); // call next animation from the queue

                if (options.hide) {
                    element.data("olddisplay", element.css("display")).hide();
                }

                this.restoreCallback();

                if (hasZoom && !transforms) {
                    setTimeout($.proxy(this, "restoreCallback"), 0); // Again jQuery callback in IE8-
                }

                for (; idx < length; idx++) {
                    effects[idx].teardown();
                }

                if (options.completeCallback) {
                    options.completeCallback(element);
                }
            }
        });

        fx.promise = function (element, options) {
            var effects = [],
            effectClass,
            effectSet = new EffectSet(element, options),
            parsedEffects = kendo.parseEffects(options.effects),
            effect;

            options.effects = parsedEffects;

            for (var effectName in parsedEffects) {
                effectClass = fx[capitalize(effectName)];

                if (effectClass) {
                    effect = new effectClass(element, parsedEffects[effectName].direction);
                    effects.push(effect);
                }
            }

            if (effects[0]) {
                effectSet.run(effects);
            } else { // Not sure how would an fx promise reach this state - means that you call kendoAnimate with no valid effects? Why?
                if (!element.is(":visible")) {
                    element.css({ display: element.data("olddisplay") || "block" }).css("display");
                }

                if (options.init) {
                    options.init();
                }

                element.dequeue();
                effectSet.complete();
            }
        };

        extend(fx, {
            animate: function (elements, properties, options) {
                var useTransition = options.transition !== false;
                delete options.transition;

                if (transitions && "transition" in fx && useTransition) {
                    fx.transition(elements, properties, options);
                } else {
                    if (transforms) {
                        elements.animate(strip3DTransforms(properties), { queue: false, show: false, hide: false, duration: options.duration, complete: options.complete }); // Stop animate from showing/hiding the element to be able to hide it later on.
                    } else {
                        elements.each(function () {
                            var element = $(this),
                            multiple = {};

                            each(transformProps, function (idx, value) { // remove transforms to avoid IE and older browsers confusion
                                var params,
                                currentValue = properties ? properties[value] + " " : null; // We need to match

                                if (currentValue) {
                                    var single = properties;

                                    if (value in scaleProperties && properties[value] !== undefined) {
                                        params = currentValue.match(cssParamsRegExp);
                                        if (transforms) {
                                            extend(single, { scale: +params[0] });
                                        }
                                    } else {
                                        if (value in translateProperties && properties[value] !== undefined) {
                                            var position = element.css(POSITION),
                                            isFixed = (position == "absolute" || position == "fixed");

                                            if (!element.data(TRANSLATE)) {
                                                if (isFixed) {
                                                    element.data(TRANSLATE, {
                                                        top: parseCSS(element, "top") || 0,
                                                        left: parseCSS(element, "left") || 0,
                                                        bottom: parseCSS(element, "bottom"),
                                                        right: parseCSS(element, "right")
                                                    });
                                                } else {
                                                    element.data(TRANSLATE, {
                                                        top: parseCSS(element, "marginTop") || 0,
                                                        left: parseCSS(element, "marginLeft") || 0
                                                    });
                                                }
                                            }

                                            var originalPosition = element.data(TRANSLATE);

                                            params = currentValue.match(cssParamsRegExp);
                                            if (params) {

                                                var dX = value == TRANSLATE + "y" ? +null : +params[1],
                                                dY = value == TRANSLATE + "y" ? +params[1] : +params[2];

                                                if (isFixed) {
                                                    if (!isNaN(originalPosition.right)) {
                                                        if (!isNaN(dX)) { extend(single, { right: originalPosition.right - dX }); }
                                                    } else {
                                                        if (!isNaN(dX)) { extend(single, { left: originalPosition.left + dX }); }
                                                    }

                                                    if (!isNaN(originalPosition.bottom)) {
                                                        if (!isNaN(dY)) { extend(single, { bottom: originalPosition.bottom - dY }); }
                                                    } else {
                                                        if (!isNaN(dY)) { extend(single, { top: originalPosition.top + dY }); }
                                                    }
                                                } else {
                                                    if (!isNaN(dX)) { extend(single, { marginLeft: originalPosition.left + dX }); }
                                                    if (!isNaN(dY)) { extend(single, { marginTop: originalPosition.top + dY }); }
                                                }
                                            }
                                        }
                                    }

                                    if (!transforms && value != "scale" && value in single) {
                                        delete single[value];
                                    }

                                    if (single) {
                                        extend(multiple, single);
                                    }
                                }
                            });

                            if (browser.msie) {
                                delete multiple.scale;
                            }

                            element.animate(multiple, { queue: false, show: false, hide: false, duration: options.duration, complete: options.complete }); // Stop animate from showing/hiding the element to be able to hide it later on.
                        });
                    }
                }
            }
        });

        fx.animatedPromise = fx.promise;

        var Effect = kendo.Class.extend({
            init: function (element, direction) {
                var that = this;
                that.element = element;
                that._direction = direction;
                that.options = {};
                that._additionalEffects = [];

                if (!that.restore) {
                    that.restore = [];
                }
            },

            // Public API
            reverse: function () {
                this._reverse = true;
                return this.run();
            },

            play: function () {
                this._reverse = false;
                return this.run();
            },

            add: function (additional) {
                this._additionalEffects.push(additional);
                return this;
            },

            direction: function (value) {
                this._direction = value;
                return this;
            },

            duration: function (duration) {
                this._duration = duration;
                return this;
            },

            compositeRun: function () {
                var that = this,
                effectSet = new EffectSet(that.element, { reverse: that._reverse, duration: that._duration }),
                effects = that._additionalEffects.concat([that]);

                return effectSet.run(effects);
            },

            run: function () {
                if (this._additionalEffects && this._additionalEffects[0]) {
                    return this.compositeRun();
                }

                var that = this,
                element = that.element,
                idx = 0,
                restore = that.restore,
                length = restore.length,
                value,
                deferred = $.Deferred(),
                start = {},
                end = {},
                target,
                children = that.children(),
                childrenLength = children.length;

                deferred.then($.proxy(that, "_complete"));

                element.data("animating", true);

                for (idx = 0; idx < length; idx++) {
                    value = restore[idx];

                    if (!element.data(value)) {
                        element.data(value, element.css(value));
                    }
                }

                for (idx = 0; idx < childrenLength; idx++) {
                    children[idx].duration(that._duration).run();
                }

                that.prepare(start, end);

                if (!element.is(":visible")) {
                    extend(start, { display: element.data("olddisplay") || "block" });
                }

                if (transforms) {
                    target = element.data("targetTransform");

                    if (target) {
                        start = extend(target, start);
                    }
                }

                start = normalizeCSS(element, start);

                if (transforms && !transitions) {
                    start = strip3DTransforms(start);
                }

                element.css(start).css(TRANSFORM); // Trick webkit into re-rendering

                that.setup();

                element.data("targetTransform", end);
                fx.animate(element, end, { duration: that._duration, complete: deferred.resolve });

                return deferred.promise();
            },

            stop: function () {
                var idx = 0,
                children = this.children(),
                childrenLength = children.length;

                for (idx = 0; idx < childrenLength; idx++) {
                    children[idx].stop();
                }

                $(this.element).kendoStop(true, true);
                return this;
            },

            restoreCallback: function () {
                var element = this.element;

                for (var i = 0, length = this.restore.length; i < length; i++) {
                    var value = this.restore[i];
                    element.css(value, element.data(value));
                }
            },

            _complete: function () {
                var that = this,
                element = that.element;

                element
                .removeData("animating")
                .dequeue(); // call next animation from the queue

                that.restoreCallback();

                if (that.shouldHide()) {
                    element.data("olddisplay", element.css("display")).hide();
                }

                if (hasZoom && !transforms) {
                    setTimeout($.proxy(that, "restoreCallback"), 0); // Again jQuery callback in IE8-
                }

                that.teardown();
            },

            /////////////////////////// Support for kendo.animate;
            setOptions: function (options) {
                extend(true, this.options, options);
            },

            children: function () {
                return [];
            },

            shouldHide: $.noop,

            setup: $.noop,
            prepare: $.noop,
            teardown: $.noop,
            directions: [],

            setReverse: function (reverse) {
                this._reverse = reverse;
                return this;
            }
        });

        function capitalize(word) {
            return word.charAt(0).toUpperCase() + word.substring(1);
        }

        function createEffect(name, definition) {
            var effectClass = Effect.extend(definition),
            directions = effectClass.prototype.directions;

            fx[capitalize(name)] = effectClass;

            fx.Element.prototype[name] = function (direction, opt1, opt2, opt3) {
                return new effectClass(this.element, direction, opt1, opt2, opt3);
            };

            each(directions, function (idx, theDirection) {
                fx.Element.prototype[name + capitalize(theDirection)] = function (opt1, opt2, opt3) {
                    return new effectClass(this.element, theDirection, opt1, opt2, opt3);
                };
            });
        }

        var FOUR_DIRECTIONS = ["left", "right", "up", "down"],
        IN_OUT = ["in", "out"];

        createEffect("slideIn", {
            directions: FOUR_DIRECTIONS,

            divisor: function (value) {
                this.options.divisor = value;
                return this;
            },

            prepare: function (start, end) {
                var that = this,
                tmp,
                element = that.element,
                direction = directions[that._direction],
                offset = -direction.modifier * (direction.vertical ? element.outerHeight() : element.outerWidth()),
                startValue = offset / (that.options && that.options.divisor || 1) + PX,
                endValue = "0px";

                if (that._reverse) {
                    tmp = start;
                    start = end;
                    end = tmp;
                }

                if (transforms) {
                    start[direction.transition] = startValue;
                    end[direction.transition] = endValue;
                } else {
                    start[direction.property] = startValue;
                    end[direction.property] = endValue;
                }
            }
        });

        createEffect("tile", {
            directions: FOUR_DIRECTIONS,

            init: function (element, direction, previous) {
                Effect.prototype.init.call(this, element, direction);
                this.options = { previous: previous };
            },

            previousDivisor: function (value) {
                this.options.previousDivisor = value;
                return this;
            },

            children: function () {
                var that = this,
                reverse = that._reverse,
                previous = that.options.previous,
                divisor = that.options.previousDivisor || 1,
                dir = that._direction;

                var children = [kendo.fx(that.element).slideIn(dir).setReverse(reverse)];

                if (previous) {
                    children.push(kendo.fx(previous).slideIn(directions[dir].reverse).divisor(divisor).setReverse(!reverse));
                }

                return children;
            }
        });

        function createToggleEffect(name, property, defaultStart, defaultEnd) {
            createEffect(name, {
                directions: IN_OUT,

                startValue: function (value) {
                    this._startValue = value;
                    return this;
                },

                endValue: function (value) {
                    this._endValue = value;
                    return this;
                },

                shouldHide: function () {
                    return this._shouldHide;
                },

                prepare: function (start, end) {
                    var that = this,
                    startValue,
                    endValue,
                    out = this._direction === "out",
                    startDataValue = that.element.data(property),
                    startDataValueIsSet = !(isNaN(startDataValue) || startDataValue == defaultStart);

                    if (startDataValueIsSet) {
                        startValue = startDataValue;
                    } else if (typeof this._startValue !== "undefined") {
                        startValue = this._startValue;
                    } else {
                        startValue = out ? defaultStart : defaultEnd;
                    }

                    if (typeof this._endValue !== "undefined") {
                        endValue = this._endValue;
                    } else {
                        endValue = out ? defaultEnd : defaultStart;
                    }

                    if (this._reverse) {
                        start[property] = endValue;
                        end[property] = startValue;
                    } else {
                        start[property] = startValue;
                        end[property] = endValue;
                    }

                    that._shouldHide = end[property] === defaultEnd;
                }
            });
        }

        createToggleEffect("fade", "opacity", 1, 0);
        createToggleEffect("zoom", "scale", 1, 0.01);

        createEffect("slideMargin", {
            prepare: function (start, end) {
                var that = this,
                element = that.element,
                options = that.options,
                origin = element.data(ORIGIN),
                offset = options.offset,
                margin,
                reverse = that._reverse;

                if (!reverse && origin === null) {
                    element.data(ORIGIN, parseFloat(element.css("margin-" + options.axis)));
                }

                margin = (element.data(ORIGIN) || 0);
                end["margin-" + options.axis] = !reverse ? margin + offset : margin;
            }
        });

        createEffect("slideTo", {
            prepare: function (start, end) {
                var that = this,
                element = that.element,
                options = that.options,
                offset = options.offset.split(","),
                reverse = that._reverse;

                if (transforms) {
                    end.translatex = !reverse ? offset[0] : 0;
                    end.translatey = !reverse ? offset[1] : 0;
                } else {
                    end.left = !reverse ? offset[0] : 0;
                    end.top = !reverse ? offset[1] : 0;
                }
                element.css("left");
            }
        });

        createEffect("expand", {
            directions: ["horizontal", "vertical"],

            restore: [OVERFLOW],

            prepare: function (start, end) {
                var that = this,
                element = that.element,
                options = that.options,
                reverse = that._reverse,
                property = that._direction === "vertical" ? HEIGHT : WIDTH,
                setLength = element[0].style[property],
                oldLength = element.data(property),
                length = parseFloat(oldLength || setLength),
                realLength = round(element.css(property, AUTO)[property]());

                start.overflow = HIDDEN;

                length = (options && options.reset) ? realLength || length : length || realLength;

                end[property] = (reverse ? 0 : length) + PX;
                start[property] = (reverse ? length : 0) + PX;

                if (oldLength === undefined) {
                    element.data(property, setLength);
                }
            },

            shouldHide: function () {
                return this._reverse;
            },

            teardown: function () {
                var that = this,
                element = that.element,
                property = that._direction === "vertical" ? HEIGHT : WIDTH,
                length = element.data(property);

                if (length == AUTO || length === BLANK) {
                    setTimeout(function () { element.css(property, AUTO).css(property); }, 0); // jQuery animate complete callback in IE is called before the last animation step!
                }
            }
        });

        var TRANSFER_START_STATE = { position: "absolute", marginLeft: 0, marginTop: 0, scale: 1 };
        /**
        * Intersection point formulas are taken from here - http://zonalandeducation.com/mmts/intersections/intersectionOfTwoLines1/intersectionOfTwoLines1.html
        * Formula for a linear function from two points from here - http://demo.activemath.org/ActiveMath2/search/show.cmd?id=mbase://AC_UK_calculus/functions/ex_linear_equation_two_points
        * The transform origin point is the intersection point of the two lines from the top left corners/top right corners of the element and target.
        * The math and variables below MAY BE SIMPLIFIED (zeroes removed), but this would make the formula too cryptic.
        */
        createEffect("transfer", {
            init: function (element, target) {
                this.element = element;
                this.options = { target: target };
                this.restore = [];
            },

            setup: function () {
                this.element.appendTo(document.body);
            },

            prepare: function (start, end) {
                var that = this,
                element = that.element,
                outerBox = fx.box(element),
                innerBox = fx.box(that.options.target),
                currentScale = animationProperty(element, "scale"),
                scale = fx.fillScale(innerBox, outerBox),
                transformOrigin = fx.transformOrigin(innerBox, outerBox);

                extend(start, TRANSFER_START_STATE);
                end.scale = 1;

                element.css(TRANSFORM, "scale(1)").css(TRANSFORM);
                element.css(TRANSFORM, "scale(" + currentScale + ")");

                start.top = outerBox.top;
                start.left = outerBox.left;
                start.transformOrigin = transformOrigin.x + PX + " " + transformOrigin.y + PX;

                if (that._reverse) {
                    start.scale = scale;
                } else {
                    end.scale = scale;
                }
            }
        });


        var CLIPS = {
            top: "rect(auto auto $size auto)",
            bottom: "rect($size auto auto auto)",
            left: "rect(auto $size auto auto)",
            right: "rect(auto auto auto $size)"
        };

        var ROTATIONS = {
            top: { start: "rotatex(0deg)", end: "rotatex(180deg)" },
            bottom: { start: "rotatex(-180deg)", end: "rotatex(0deg)" },
            left: { start: "rotatey(0deg)", end: "rotatey(-180deg)" },
            right: { start: "rotatey(180deg)", end: "rotatey(0deg)" }
        };

        function clipInHalf(container, direction) {
            var vertical = kendo.directions[direction].vertical,
            size = (container[vertical ? HEIGHT : WIDTH]() / 2) + "px";

            return CLIPS[direction].replace("$size", size);
        }

        createEffect("turningPage", {
            directions: FOUR_DIRECTIONS,

            init: function (element, direction, container) {
                Effect.prototype.init.call(this, element, direction);
                this._container = container;
            },

            prepare: function (start, end) {
                var that = this,
                reverse = that._reverse,
                direction = reverse ? directions[that._direction].reverse : that._direction,
                rotation = ROTATIONS[direction];

                start.zIndex = 1;

                if (that._clipInHalf) {
                    start.clip = clipInHalf(that._container, kendo.directions[direction].reverse);
                }

                start[BACKFACE] = HIDDEN;

                end[TRANSFORM] = TRANSFORM_PERSPECTIVE + (reverse ? rotation.start : rotation.end);
                start[TRANSFORM] = TRANSFORM_PERSPECTIVE + (reverse ? rotation.end : rotation.start);
            },

            setup: function () {
                this._container.append(this.element);
            },

            face: function (value) {
                this._face = value;
                return this;
            },

            shouldHide: function () {
                var that = this,
                reverse = that._reverse,
                face = that._face;

                return (reverse && !face) || (!reverse && face);
            },

            clipInHalf: function (value) {
                this._clipInHalf = value;
                return this;
            },

            temporary: function () {
                this.element.addClass('temp-page');
                return this;
            }
        });

        createEffect("staticPage", {
            directions: FOUR_DIRECTIONS,

            init: function (element, direction, container) {
                Effect.prototype.init.call(this, element, direction);
                this._container = container;
            },

            restore: ["clip"],

            prepare: function (start, end) {
                var that = this,
                direction = that._reverse ? directions[that._direction].reverse : that._direction;

                start.clip = clipInHalf(that._container, direction);
                start.opacity = 0.999;
                end.opacity = 1;
            },

            shouldHide: function () {
                var that = this,
                reverse = that._reverse,
                face = that._face;

                return (reverse && !face) || (!reverse && face);
            },

            face: function (value) {
                this._face = value;
                return this;
            }
        });

        createEffect("pageturn", {
            directions: ["horizontal", "vertical"],

            init: function (element, direction, face, back) {
                Effect.prototype.init.call(this, element, direction);
                this.options = {};
                this.options.face = face;
                this.options.back = back;
            },

            children: function () {
                var that = this,
                options = that.options,
                direction = that._direction === "horizontal" ? "left" : "top",
                reverseDirection = kendo.directions[direction].reverse,
                reverse = that._reverse,
                temp,
                faceClone = options.face.clone(true).removeAttr("id"),
                backClone = options.back.clone(true).removeAttr("id"),
                element = that.element;

                if (reverse) {
                    temp = direction;
                    direction = reverseDirection;
                    reverseDirection = temp;
                }

                return [
                kendo.fx(options.face).staticPage(direction, element).face(true).setReverse(reverse),
                kendo.fx(options.back).staticPage(reverseDirection, element).setReverse(reverse),
                kendo.fx(faceClone).turningPage(direction, element).face(true).clipInHalf(true).temporary().setReverse(reverse),
                kendo.fx(backClone).turningPage(reverseDirection, element).clipInHalf(true).temporary().setReverse(reverse)
            ];
            },

            prepare: function (start, end) {
                start[PERSPECTIVE] = DEFAULT_PERSPECTIVE;
                start.transformStyle = "preserve-3d";
                // hack to trigger transition end.
                start.opacity = 0.999;
                end.opacity = 1;
            },

            teardown: function () {
                this.element.find(".temp-page").remove();
            }
        });

        createEffect("flip", {
            directions: ["horizontal", "vertical"],

            init: function (element, direction, face, back) {
                Effect.prototype.init.call(this, element, direction);
                this.options = {};
                this.options.face = face;
                this.options.back = back;
            },

            children: function () {
                var that = this,
                options = that.options,
                direction = that._direction === "horizontal" ? "left" : "top",
                reverseDirection = kendo.directions[direction].reverse,
                reverse = that._reverse,
                temp,
                element = that.element;

                if (reverse) {
                    temp = direction;
                    direction = reverseDirection;
                    reverseDirection = temp;
                }

                return [
                kendo.fx(options.face).turningPage(direction, element).face(true).setReverse(reverse),
                kendo.fx(options.back).turningPage(reverseDirection, element).setReverse(reverse)
            ];
            },

            prepare: function (start) {
                start[PERSPECTIVE] = DEFAULT_PERSPECTIVE;
                start.transformStyle = "preserve-3d";
            }
        });

        var RESTORE_OVERFLOW = !support.mobileOS.android;
        var IGNORE_TRANSITION_EVENT_SELECTOR = ".km-touch-scrollbar, .km-actionsheet-wrapper";

        createEffect("replace", {
            _before: $.noop,
            _after: $.noop,
            init: function (element, previous, transitionClass) {
                Effect.prototype.init.call(this, element);
                this._previous = $(previous);
                this._transitionClass = transitionClass;
            },

            duration: function () {
                throw new Error("The replace effect does not support duration setting; the effect duration may be customized through the transition class rule");
            },

            beforeTransition: function (callback) {
                this._before = callback;
                return this;
            },

            afterTransition: function (callback) {
                this._after = callback;
                return this;
            },

            _both: function () {
                return $().add(this._element).add(this._previous);
            },

            _containerClass: function () {
                var direction = this._direction,
                containerClass = "k-fx k-fx-start k-fx-" + this._transitionClass;

                if (direction) {
                    containerClass += " k-fx-" + direction;
                }

                if (this._reverse) {
                    containerClass += " k-fx-reverse";
                }

                return containerClass;
            },

            complete: function (e) {
                if (!this.deferred || (e && $(e.target).is(IGNORE_TRANSITION_EVENT_SELECTOR))) {
                    return;
                }

                var container = this.container;

                container
                .removeClass("k-fx-end")
                .removeClass(this._containerClass())
                .off(transitions.event, this.completeProxy);

                this._previous.hide().removeClass("k-fx-current");
                this.element.removeClass("k-fx-next");

                if (RESTORE_OVERFLOW) {
                    container.css(OVERFLOW, "");
                }

                if (!this.isAbsolute) {
                    this._both().css(POSITION, "");
                }

                this.deferred.resolve();
                delete this.deferred;
            },

            run: function () {
                if (this._additionalEffects && this._additionalEffects[0]) {
                    return this.compositeRun();
                }

                var that = this,
                element = that.element,
                previous = that._previous,
                container = element.parents().filter(previous.parents()).first(),
                both = that._both(),
                deferred = $.Deferred(),
                originalPosition = element.css(POSITION),
                originalOverflow;

                // edge case for grid/scheduler, where the previous is already destroyed.
                if (!container.length) {
                    container = element.parent();
                }

                this.container = container;
                this.deferred = deferred;
                this.isAbsolute = originalPosition == "absolute";

                if (!this.isAbsolute) {
                    both.css(POSITION, "absolute");
                }

                if (RESTORE_OVERFLOW) {
                    originalOverflow = container.css(OVERFLOW);
                    container.css(OVERFLOW, "hidden");
                }

                if (!transitions) {
                    this.complete();
                } else {
                    element.addClass("k-fx-hidden");

                    container.addClass(this._containerClass());

                    this.completeProxy = $.proxy(this, "complete");
                    container.on(transitions.event, this.completeProxy);

                    kendo.animationFrame(function () {
                        element.removeClass("k-fx-hidden").addClass("k-fx-next");
                        previous.css("display", "").addClass("k-fx-current");
                        that._before(previous, element);
                        kendo.animationFrame(function () {
                            container.removeClass("k-fx-start").addClass("k-fx-end");
                            that._after(previous, element);
                        });
                    });
                }

                return deferred.promise();
            },

            stop: function () {
                this.complete();
            }
        });

        var Animation = kendo.Class.extend({
            init: function () {
                var that = this;
                that._tickProxy = proxy(that._tick, that);
                that._started = false;
            },

            tick: $.noop,
            done: $.noop,
            onEnd: $.noop,
            onCancel: $.noop,

            start: function () {
                if (!this.enabled()) {
                    return;
                }

                if (!this.done()) {
                    this._started = true;
                    kendo.animationFrame(this._tickProxy);
                } else {
                    this.onEnd();
                }
            },

            enabled: function () {
                return true;
            },

            cancel: function () {
                this._started = false;
                this.onCancel();
            },

            _tick: function () {
                var that = this;
                if (!that._started) { return; }

                that.tick();

                if (!that.done()) {
                    kendo.animationFrame(that._tickProxy);
                } else {
                    that._started = false;
                    that.onEnd();
                }
            }
        });

        var Transition = Animation.extend({
            init: function (options) {
                var that = this;
                extend(that, options);
                Animation.fn.init.call(that);
            },

            done: function () {
                return this.timePassed() >= this.duration;
            },

            timePassed: function () {
                return Math.min(this.duration, (new Date()) - this.startDate);
            },

            moveTo: function (options) {
                var that = this,
                movable = that.movable;

                that.initial = movable[that.axis];
                that.delta = options.location - that.initial;

                that.duration = typeof options.duration == "number" ? options.duration : 300;

                that.tick = that._easeProxy(options.ease);

                that.startDate = new Date();
                that.start();
            },

            _easeProxy: function (ease) {
                var that = this;

                return function () {
                    that.movable.moveAxis(that.axis, ease(that.timePassed(), that.initial, that.delta, that.duration));
                };
            }
        });

        extend(Transition, {
            easeOutExpo: function (t, b, c, d) {
                return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
            },

            easeOutBack: function (t, b, c, d, s) {
                s = 1.70158;
                return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
            }
        });

        fx.Animation = Animation;
        fx.Transition = Transition;
        fx.createEffect = createEffect;

        fx.box = function (element) {
            element = $(element);
            var result = element.offset();
            result.width = element.outerWidth();
            result.height = element.outerHeight();
            return result;
        };

        fx.transformOrigin = function (inner, outer) {
            var x = (inner.left - outer.left) * outer.width / (outer.width - inner.width),
            y = (inner.top - outer.top) * outer.height / (outer.height - inner.height);

            return {
                x: isNaN(x) ? 0 : x,
                y: isNaN(y) ? 0 : y
            };
        };

        fx.fillScale = function (inner, outer) {
            return Math.min(inner.width / outer.width, inner.height / outer.height);
        };

        fx.fitScale = function (inner, outer) {
            return Math.max(inner.width / outer.width, inner.height / outer.height);
        };
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Observable = kendo.Observable,
        SCRIPT = "SCRIPT",
        INIT = "init",
        SHOW = "show",
        HIDE = "hide",
        TRANSITION_START = "transitionStart",
        TRANSITION_END = "transitionEnd",

        ATTACH = "attach",
        DETACH = "detach",
        sizzleErrorRegExp = /unrecognized expression/;

        var View = Observable.extend({
            init: function (content, options) {
                var that = this;
                options = options || {};

                Observable.fn.init.call(that);
                that.content = content;
                that.id = kendo.guid();
                that.tagName = options.tagName || "div";
                that.model = options.model;
                that._wrap = options.wrap !== false;
                this._evalTemplate = options.evalTemplate || false;
                that._fragments = {};

                that.bind([INIT, SHOW, HIDE, TRANSITION_START, TRANSITION_END], options);
            },

            render: function (container) {
                var that = this,
                notInitialized = !that.element;

                // The order below matters - kendo.bind should happen when the element is in the DOM, and show should be triggered after init.

                if (notInitialized) {
                    that.element = that._createElement();
                }

                if (container) {
                    $(container).append(that.element);
                }

                if (notInitialized) {
                    kendo.bind(that.element, that.model);
                    that.trigger(INIT);
                }

                if (container) {
                    that._eachFragment(ATTACH);
                    that.trigger(SHOW);
                }

                return that.element;
            },

            clone: function (back) {
                return new ViewClone(this);
            },

            triggerBeforeShow: function () {
                return true;
            },

            showStart: function () {
                this.element.css("display", "");
            },

            showEnd: function () {
            },

            hideStart: function () {
            },

            hideEnd: function () {
                this.hide();
            },

            beforeTransition: function (type) {
                this.trigger(TRANSITION_START, { type: type });
            },

            afterTransition: function (type) {
                this.trigger(TRANSITION_END, { type: type });
            },

            hide: function () {
                this._eachFragment(DETACH);
                this.element.detach();
                this.trigger(HIDE);
            },

            destroy: function () {
                var element = this.element;

                if (element) {
                    kendo.unbind(element);
                    kendo.destroy(element);
                    element.remove();
                }
            },

            fragments: function (fragments) {
                $.extend(this._fragments, fragments);
            },

            _eachFragment: function (methodName) {
                for (var placeholder in this._fragments) {
                    this._fragments[placeholder][methodName](this, placeholder);
                }
            },

            _createElement: function () {
                var that = this,
                wrapper = "<" + that.tagName + " />",
                element,
                content;

                try {
                    content = $(document.getElementById(that.content) || that.content); // support passing id without #

                    if (content[0].tagName === SCRIPT) {
                        content = content.html();
                    }
                } catch (e) {
                    if (sizzleErrorRegExp.test(e.message)) {
                        content = that.content;
                    }
                }

                if (typeof content === "string") {
                    content = content.replace(/^\s+|\s+$/g, '');
                    if (that._evalTemplate) {
                        content = kendo.template(content)(that.model || {});
                    }

                    element = $(wrapper).append(content);
                    // drop the wrapper if asked - this seems like the easiest (although not very intuitive) way to avoid messing up templates with questionable content, like this one for instance:
                    // <script id="my-template">
                    // foo
                    // <span> Span </span>
                    // </script>
                    if (!that._wrap) {
                        element = element.contents();
                    }
                } else {
                    element = content;
                    if (that._wrap) {
                        element = element.wrapAll(wrapper).parent();
                    }
                }

                return element;
            }
        });

        var ViewClone = kendo.Class.extend({
            init: function (view) {
                $.extend(this, {
                    element: view.element.clone(true),
                    transition: view.transition,
                    id: view.id
                });

                view.element.parent().append(this.element);
            },

            hideStart: $.noop,

            hideEnd: function () {
                this.element.remove();
            },

            beforeTransition: $.noop,
            afterTransition: $.noop
        });

        var Layout = View.extend({
            init: function (content, options) {
                View.fn.init.call(this, content, options);
                this.containers = {};
            },

            container: function (selector) {
                var container = this.containers[selector];

                if (!container) {
                    container = this._createContainer(selector);
                    this.containers[selector] = container;
                }

                return container;
            },

            showIn: function (selector, view, transition) {
                this.container(selector).show(view, transition);
            },

            _createContainer: function (selector) {
                var root = this.render(),
                element = root.find(selector),
                container;

                if (!element.length && root.is(selector)) {
                    if (root.is(selector)) {
                        element = root;
                    } else {

                        throw new Error("can't find a container with the specified " + selector + " selector");
                    }
                }

                container = new ViewContainer(element);

                container.bind("accepted", function (e) {
                    e.view.render(element);
                });

                return container;
            }
        });

        var Fragment = View.extend({
            attach: function (view, placeholder) {
                view.element.find(placeholder).replaceWith(this.render());
            },

            detach: function () {
            }
        });

        var transitionRegExp = /^(\w+)(:(\w+))?( (\w+))?$/;

        function parseTransition(transition) {
            if (!transition) {
                return {};
            }

            var matches = transition.match(transitionRegExp) || [];

            return {
                type: matches[1],
                direction: matches[3],
                reverse: matches[5] === "reverse"
            };
        }

        var ViewContainer = Observable.extend({
            init: function (container) {
                Observable.fn.init.call(this);
                this.container = container;
                this.history = [];
                this.view = null;
                this.running = false;
            },

            after: function () {
                this.running = false;
                this.trigger("complete", { view: this.view });
                this.trigger("after");
            },

            end: function () {
                this.view.showEnd();
                this.previous.hideEnd();
                this.after();
            },

            show: function (view, transition, locationID) {
                if (!view.triggerBeforeShow()) {
                    this.trigger("after");
                    return false;
                }

                locationID = locationID || view.id;

                var that = this,
                current = (view === that.view) ? view.clone() : that.view,
                history = that.history,
                previousEntry = history[history.length - 2] || {},
                back = previousEntry.id === locationID,
                // If explicit transition is set, it will be with highest priority
                // Next we will try using the history record transition or the view transition configuration
                theTransition = transition || (back ? history[history.length - 1].transition : view.transition),
                transitionData = parseTransition(theTransition);

                if (that.running) {
                    that.effect.stop();
                }

                if (theTransition === "none") {
                    theTransition = null;
                }

                that.trigger("accepted", { view: view });
                that.view = view;
                that.previous = current;
                that.running = true;

                if (!back) {
                    history.push({ id: locationID, transition: theTransition });
                } else {
                    history.pop();
                }

                if (!current) {
                    view.showStart();
                    view.showEnd();
                    that.after();
                    return true;
                }

                current.hideStart();

                if (!theTransition || !kendo.effects.enabled) {
                    view.showStart();
                    that.end();
                } else {
                    // hide the view element before init/show - prevents blinks on iPad
                    // the replace effect will remove this class
                    view.element.addClass("k-fx-hidden");
                    view.showStart();
                    // do not reverse the explicit transition
                    if (back && !transition) {
                        transitionData.reverse = !transitionData.reverse;
                    }

                    that.effect = kendo.fx(view.element).replace(current.element, transitionData.type)
                    .beforeTransition(function () {
                        view.beforeTransition("show");
                        current.beforeTransition("hide");
                    })
                    .afterTransition(function () {
                        view.afterTransition("show");
                        current.afterTransition("hide");
                    })
                    .direction(transitionData.direction)
                    .setReverse(transitionData.reverse);

                    that.effect.run().then(function () { that.end(); });
                }

                return true;
            }
        });

        kendo.ViewContainer = ViewContainer;
        kendo.Fragment = Fragment;
        kendo.Layout = Layout;
        kendo.View = View;
        kendo.ViewClone = ViewClone;

    })(window.kendo.jQuery);





    (function (kendo) {
        function Node() {
            this.node = null;
        }

        Node.prototype = {
            remove: function () {
                this.node.parentNode.removeChild(this.node);
            }
        };

        function Element(nodeName, attr, children) {
            this.nodeName = nodeName;

            this.attr = attr || {};

            this.cssText = null;

            this.children = children || [];
        }

        Element.prototype = new Node();

        Element.prototype.render = function (parent, cached) {
            var node;

            var index;

            var children = this.children;

            var length = children.length;

            if (!cached || cached.nodeName !== this.nodeName) {
                if (cached) {
                    cached.remove();
                    cached = null;
                }

                node = document.createElement(this.nodeName);

                for (index = 0; index < length; index++) {
                    children[index].render(node, null);
                }

                parent.appendChild(node);
            } else {
                node = cached.node;

                var cachedChildren = cached.children;

                if (Math.abs(cachedChildren.length - length) > 2) {
                    this.render({
                        appendChild: function (node) {
                            parent.replaceChild(node, cached.node);
                        }
                    }, null);

                    return;
                }

                for (index = 0; index < length; index++) {
                    children[index].render(node, cachedChildren[index]);
                }

                for (index = length, length = cachedChildren.length; index < length; index++) {
                    cachedChildren[index].remove();
                }
            }

            var attr = this.attr;
            var attrName;

            for (attrName in attr) {
                if (!cached || attr[attrName] !== cached.attr[attrName]) {
                    if (node[attrName] !== undefined) {
                        if (attrName !== "style") {
                            node[attrName] = attr[attrName];
                        } else {
                            var cssText = "";

                            var style = attr[attrName];

                            for (var key in style) {
                                cssText += key;
                                cssText += ":";
                                cssText += style[key];
                                cssText += ";";
                            }

                            if (!cached || cached.cssText !== cssText) {
                                node.style.cssText = cssText;
                            }

                            this.cssText = cssText;
                        }
                    } else {
                        node.setAttribute(attrName, attr[attrName]);
                    }
                }
            }

            if (cached) {
                for (attrName in cached.attr) {
                    if (attr[attrName] === undefined) {
                        if (node[attrName] !== undefined) {
                            if (attrName === "style") {
                                node.style.cssText = "";
                            } else if (attrName === "className") {
                                node[attrName] = "";
                            } else {
                                node.removeAttribute(attrName);
                            }
                        } else {
                            node.removeAttribute(attrName);
                        }
                    }
                }
            }

            this.node = node;
        };

        function TextNode(nodeValue) {
            this.nodeValue = nodeValue;
        }

        TextNode.prototype = new Node();

        TextNode.prototype.nodeName = "#text";

        TextNode.prototype.render = function (parent, cached) {
            var node;

            if (!cached || cached.nodeName !== this.nodeName) {
                if (cached) {
                    cached.remove();
                }
                node = document.createTextNode(this.nodeValue);

                parent.appendChild(node);
            } else {
                node = cached.node;

                if (this.nodeValue !== cached.nodeValue) {
                    node.nodeValue = this.nodeValue;
                }
            }

            this.node = node;
        };

        function HtmlNode(html) {
            this.html = html;
        }

        HtmlNode.prototype = {
            nodeName: "#html",
            remove: function () {
                for (var index = 0; index < this.nodes.length; index++) {
                    this.nodes[index].parentNode.removeChild(this.nodes[index]);
                }
            },
            render: function (parent, cached) {
                if (!cached || cached.nodeName !== this.nodeName || cached.html !== this.html) {
                    if (cached) {
                        cached.remove();
                    }

                    var lastChild = parent.lastChild;

                    parent.insertAdjacentHTML("beforeend", this.html);

                    this.nodes = [];

                    for (var child = lastChild ? lastChild.nextSibling : parent.firstChild; child; child = child.nextSibling) {
                        this.nodes.push(child);
                    }
                } else {
                    this.nodes = cached.nodes.slice(0);
                }
            }
        };

        function html(value) {
            return new HtmlNode(value);
        }

        function element(nodeName, attrs, children) {
            return new Element(nodeName, attrs, children);
        }

        function text(value) {
            return new TextNode(value);
        }

        function Tree(root) {
            this.root = root;
            this.children = [];
        }

        Tree.prototype = {
            html: html,
            element: element,
            text: text,
            render: function (children) {
                var cachedChildren = this.children;

                var index;

                var length;

                for (index = 0, length = children.length; index < length; index++) {
                    children[index].render(this.root, cachedChildren[index]);
                }

                for (index = length; index < cachedChildren.length; index++) {
                    cachedChildren[index].remove();
                }

                this.children = children;
            }
        };

        kendo.dom = {
            html: html,
            text: text,
            element: element,
            Tree: Tree
        };
    })(window.kendo);





    (function (kendo) {

        var RELS = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n' +
           '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
               '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>' +
               '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>' +
               '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' +
            '</Relationships>';

        var CORE = kendo.template(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n' +
'<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" ' +
  'xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" ' +
  'xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
   '<dc:creator>${creator}</dc:creator>' +
   '<cp:lastModifiedBy>${lastModifiedBy}</cp:lastModifiedBy>' +
   '<dcterms:created xsi:type="dcterms:W3CDTF">${created}</dcterms:created>' +
   '<dcterms:modified xsi:type="dcterms:W3CDTF">${modified}</dcterms:modified>' +
'</cp:coreProperties>');

        var APP = kendo.template(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n' +
'<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">' +
  '<Application>Microsoft Excel</Application>' +
  '<DocSecurity>0</DocSecurity>' +
  '<ScaleCrop>false</ScaleCrop>' +
  '<HeadingPairs>' +
      '<vt:vector size="2" baseType="variant">' +
          '<vt:variant>' +
              '<vt:lpstr>Worksheets</vt:lpstr>' +
          '</vt:variant>' +
          '<vt:variant>' +
              '<vt:i4>${sheets.length}</vt:i4>' +
          '</vt:variant>' +
      '</vt:vector>' +
  '</HeadingPairs>' +
  '<TitlesOfParts>' +
      '<vt:vector size="${sheets.length}" baseType="lpstr">' +
      '# for (var idx = 0; idx < sheets.length; idx++) { #' +
          '# if (sheets[idx].options.title) { #' +
          '<vt:lpstr>${sheets[idx].options.title}</vt:lpstr>' +
          '# } else { #' +
          '<vt:lpstr>Sheet${idx+1}</vt:lpstr>' +
          '# } #' +
      '# } #' +
      '</vt:vector>' +
  '</TitlesOfParts>' +
  '<LinksUpToDate>false</LinksUpToDate>' +
  '<SharedDoc>false</SharedDoc>' +
  '<HyperlinksChanged>false</HyperlinksChanged>' +
  '<AppVersion>14.0300</AppVersion>' +
'</Properties>');

        var CONTENT_TYPES = kendo.template(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n' +
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
   '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />' +
   '<Default Extension="xml" ContentType="application/xml" />' +
   '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" />' +
   '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>' +
   '<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>' +
   '# for (var idx = 1; idx <= count; idx++) { #' +
   '<Override PartName="/xl/worksheets/sheet${idx}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" />' +
   '# } #' +
   '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml" />' +
   '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" />' +
'</Types>');

        var WORKBOOK = kendo.template(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n' +
'<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' +
  '<fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="9303" />' +
  '<workbookPr defaultThemeVersion="124226" />' +
  '<bookViews>' +
      '<workbookView xWindow="240" yWindow="45" windowWidth="18195" windowHeight="7995" />' +
  '</bookViews>' +
  '<sheets>' +
  '# for (var idx = 0; idx < sheets.length; idx++) { #' +
      '# if (sheets[idx].options.title) { #' +
      '<sheet name="${sheets[idx].options.title}" sheetId="${idx+1}" r:id="rId${idx+1}" />' +
      '# } else { #' +
      '<sheet name="Sheet${idx+1}" sheetId="${idx+1}" r:id="rId${idx+1}" />' +
      '# } #' +
  '# } #' +
  '</sheets>' +
  '<calcPr calcId="145621" />' +
'</workbook>');

        var WORKSHEET = kendo.template(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n' +
'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" mc:Ignorable="x14ac">' +
   '<dimension ref="A1" />' +
   '<sheetViews>' +
       '<sheetView tabSelected="1" workbookViewId="0">' +
       '# if (freezePane) { #' +
       '<pane state="frozen"' +
       '# if (freezePane.colSplit) { #' +
       ' xSplit="${freezePane.colSplit}"' +
       '# } #' +
       '# if (freezePane.rowSplit) { #' +
       ' ySplit="${freezePane.rowSplit}"' +
       '# } #' +
       ' topLeftCell="${String.fromCharCode(65 + (freezePane.colSplit || 0))}${(freezePane.rowSplit || 0)+1}"' +
       '/>' +
       '# } #' +
       '</sheetView>' +
   '</sheetViews>' +
   '<sheetFormatPr defaultRowHeight="15" x14ac:dyDescent="0.25" />' +
   '# if (columns) { #' +
   '<cols>' +
   '# for (var ci = 0; ci < columns.length; ci++) { #' +
       '# var column = columns[ci]; #' +
       '# if (column.width) { #' +
       '<col min="${ci+1}" max="${ci+1}" customWidth="1"' +
       '# if (column.autoWidth) { #' +
       ' width="${((column.width*7+5)/7*256)/256}" bestFit="1"' +
       '# } else { #' +
       ' width="${(((column.width)/7)*100+0.5)/100}" ' +
       '# } #' +
       '/>' +
       '# } #' +
   '# } #' +
   '</cols>' +
   '# } #' +
   '<sheetData>' +
   '# for (var ri = 0; ri < data.length; ri++) { #' +
       '# var row = data[ri]; #' +
       '<row r="${ri + 1}">' +
       '# for (var ci = 0; ci < row.data.length; ci++) { #' +
           '# var cell = row.data[ci];#' +
           '<c r="${cell.ref}"# if (cell.style) { # s="${cell.style}" # } ## if (cell.type) { # t="${cell.type}"# } #>' +
           '# if (cell.value != null) { #' +
               '<v>${cell.value}</v>' +
           '# } #' +
           '</c>' +
       '# } #' +
       '</row>' +
   '# } #' +
   '</sheetData>' +
   '# if (filter) { #' +
   '<autoFilter ref="${filter.from}:${filter.to}"/>' +
   '# } #' +
   '# if (mergeCells.length) { #' +
   '<mergeCells count="${mergeCells.length}">' +
       '# for (var ci = 0; ci < mergeCells.length; ci++) { #' +
       '<mergeCell ref="${mergeCells[ci]}"/>' +
       '# } #' +
   '</mergeCells>' +
   '# } #' +
   '<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3" />' +
'</worksheet>');

        var WORKBOOK_RELS = kendo.template(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n' +
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
'# for (var idx = 1; idx <= count; idx++) { #' +
   '<Relationship Id="rId${idx}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${idx}.xml" />' +
'# } #' +
   '<Relationship Id="rId${count+1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" />' +
   '<Relationship Id="rId${count+2}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml" />' +
'</Relationships>');

        var SHARED_STRINGS = kendo.template(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n' +
'<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${count}" uniqueCount="${uniqueCount}">' +
'# for (var index in indexes) { #' +
    '<si><t>${index.substring(1)}</t></si>' +
'# } #' +
'</sst>');

        var STYLES = kendo.template(
'<?xml version="1.0" encoding="UTF-8"?>' +
'<styleSheet' +
   ' xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"' +
   ' xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"' +
   ' mc:Ignorable="x14ac"' +
   ' xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">' +
   '<numFmts count="${formats.length}">' +
   '# for (var fi = 0; fi < formats.length; fi++) { #' +
       '# var format = formats[fi]; #' +
       '<numFmt formatCode="${format.format}" numFmtId="${165+fi}" />' +
   '# } #' +
   '</numFmts>' +
   '<fonts count="${fonts.length+1}" x14ac:knownFonts="1">' +
      '<font>' +
         '<sz val="11" />' +
         '<color theme="1" />' +
         '<name val="Calibri" />' +
         '<family val="2" />' +
         '<scheme val="minor" />' +
      '</font>' +
   '# for (var fi = 0; fi < fonts.length; fi++) { #' +
       '# var font = fonts[fi]; #' +
      '<font>' +
         '# if (font.bold) { #' +
            '<b/>' +
         '# } #' +
         '# if (font.italic) { #' +
            '<i/>' +
         '# } #' +
         '# if (font.underline) { #' +
            '<u/>' +
         '# } #' +
         '# if (font.color) { #' +
         '<color rgb="${font.color}" />' +
         '# } else { #' +
         '<color theme="1" />' +
         '# } #' +
         '# if (font.fontSize) { #' +
         '<sz val="${font.fontSize}" />' +
         '# } else { #' +
         '<sz val="11" />' +
         '# } #' +
         '# if (font.fontName) { #' +
         '<name val="${font.fontName}" />' +
         '# } else { #' +
         '<name val="Calibri" />' +
         '<scheme val="minor" />' +
         '# } #' +
         '<family val="2" />' +
      '</font>' +
   '# } #' +
   '</fonts>' +
    '<fills count="${fills.length+1}">' +
        '<fill><patternFill patternType="none"/></fill>' +
        '<fill><patternFill patternType="gray125"/></fill>' +
    '# for (var fi = 0; fi < fills.length; fi++) { #' +
       '# var fill = fills[fi]; #' +
       '# if (fill.background) { #' +
        '<fill>' +
            '<patternFill patternType="solid">' +
                '<fgColor rgb="${fill.background}"/>' +
            '</patternFill>' +
        '</fill>' +
       '# } #' +
    '# } #' +
    '</fills>' +
    '<borders count="1">' +
        '<border><left/><right/><top/><bottom/><diagonal/></border>' +
    '</borders>' +
   '<cellXfs count="${styles.length+1}">' +
       '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>' +
   '# for (var si = 0; si < styles.length; si++) { #' +
       '# var style = styles[si]; #' +
       '<xf xfid="0"' +
       '# if (style.fontId) { #' +
          ' fontId="${style.fontId}" applyFont="1"' +
       '# } #' +
       '# if (style.fillId) { #' +
          ' fillId="${style.fillId}" applyFill="1"' +
       '# } #' +
       '# if (style.numFmtId) { #' +
          ' numFmtId="${style.numFmtId}" applyNumberFormat="1"' +
       '# } #' +
       '# if (style.hAlign || style.vAlign) { #' +
       ' applyAlignment="1"' +
       '# } #' +
       '>' +
       '# if (style.hAlign || style.vAlign) { #' +
       '<alignment' +
       '# if (style.hAlign) { #' +
       ' horizontal="${style.hAlign}"' +
       '# } #' +
       '# if (style.vAlign) { #' +
       ' vertical="${style.vAlign}"' +
       '# } #' +
       '/>' +
       '# } #' +
       '</xf>' +
   '# } #' +
   '</cellXfs>' +
   '<dxfs count="0" />' +
   '<tableStyles count="0" defaultTableStyle="TableStyleMedium2" defaultPivotStyle="PivotStyleMedium9" />' +
'</styleSheet>');

        function numChar(colIndex) {
            var letter = Math.floor(colIndex / 26) - 1;

            return (letter >= 0 ? numChar(letter) : "") + String.fromCharCode(65 + (colIndex % 26));
        }

        function ref(rowIndex, colIndex) {
            return numChar(colIndex) + (rowIndex + 1);
        }

        var Worksheet = kendo.Class.extend({
            init: function (options, sharedStrings, styles) {
                this.options = options;
                this._strings = sharedStrings;
                this._styles = styles;
                this._mergeCells = [];
            },
            toXML: function () {
                var rows = this.options.rows || [];
                var filter = this.options.filter;

                return WORKSHEET({
                    freezePane: this.options.freezePane,
                    columns: this.options.columns,
                    data: $.map(rows, $.proxy(this._row, this, rows)),
                    mergeCells: this._mergeCells,
                    filter: filter ? { from: ref(0, filter.from), to: ref(0, filter.to)} : null
                });
            },
            _row: function (rows, row, rowIndex) {
                this._cellIndex = 0;

                return {
                    data: $.map(row.cells, $.proxy(this._cell, this, rows, rowIndex))
                };
            },
            _lookupString: function (value) {
                var key = "$" + value;
                var index = this._strings.indexes[key];

                if (index !== undefined) {
                    value = index;
                } else {
                    value = this._strings.indexes[key] = this._strings.uniqueCount;
                    this._strings.uniqueCount++;
                }

                this._strings.count++;

                return value;
            },
            _lookupStyle: function (style) {
                var json = kendo.stringify(style);

                if (json == "{}") {
                    return 0;
                }

                var index = $.inArray(json, this._styles);

                if (index < 0) {
                    index = this._styles.push(json) - 1;
                }

                // There is one default style
                return index + 1;
            },
            _cell: function (rows, rowIndex, data) {
                if (!data) {
                    this._cellIndex++;
                    return;
                }

                var value = data.value;

                var style = {
                    bold: data.bold,
                    color: data.color,
                    background: data.background,
                    italic: data.italic,
                    underline: data.underline,
                    fontName: data.fontName,
                    fontSize: data.fontSize,
                    format: data.format,
                    hAlign: data.hAlign,
                    vAlign: data.vAlign
                };

                var columns = this.options.columns || [];

                var column = columns[this._cellIndex];

                if (column && column.autoWidth) {
                    column.width = Math.max(column.width || 0, ("" + value).length);
                }

                var type = typeof value;

                if (type === "string") {
                    value = this._lookupString(value);
                    type = "s";
                } else if (type === "number") {
                    type = "n";
                } else if (type === "boolean") {
                    type = "b";
                    value = +value;
                } else if (value && value.getTime) {
                    type = "d";
                    value = kendo.timezone.remove(value, "Etc/UTC").toJSON();
                    if (!style.format) {
                        style.format = "mm-dd-yy";
                    }
                } else {
                    type = null;
                    value = "";
                }

                style = this._lookupStyle(style);

                var cell = {
                    value: value,
                    type: type,
                    style: style,
                    ref: ref(rowIndex, this._cellIndex)
                };

                var colSpan = data.colSpan || 1;
                var rowSpan = data.rowSpan || 1;

                if (colSpan > 1 || rowSpan > 1) {
                    var cells = [cell];

                    for (var ci = 1; ci < colSpan; ci++) {
                        this._cellIndex++;

                        cells[ci] = { ref: ref(rowIndex, this._cellIndex) };
                    }

                    for (var ri = 1; ri < rowSpan; ri++) {
                        if (rows[rowIndex + ri]) {
                            rows[rowIndex + ri].cells.splice(this._cellIndex, 0, {});
                        }
                    }

                    this._mergeCells.push(cell.ref + ":" + ref(rowIndex + rowSpan - 1, this._cellIndex));

                    cell = cells;
                }

                this._cellIndex++;

                return cell;
            }
        });

        var defaultFormats = {
            "General": 0,
            "0": 1,
            "0.00": 2,
            "#,##0": 3,
            "#,##0.00": 4,
            "0%": 9,
            "0.00%": 10,
            "0.00E+00": 11,
            "# ?/?": 12,
            "# ??/??": 13,
            "mm-dd-yy": 14,
            "d-mmm-yy": 15,
            "d-mmm": 16,
            "mmm-yy": 17,
            "h:mm AM/PM": 18,
            "h:mm:ss AM/PM": 19,
            "h:mm": 20,
            "h:mm:ss": 21,
            "m/d/yy h:mm": 22,
            "#,##0 ;(#,##0)": 37,
            "#,##0 ;[Red](#,##0)": 38,
            "#,##0.00;(#,##0.00)": 39,
            "#,##0.00;[Red](#,##0.00)": 40,
            "mm:ss": 45,
            "[h]:mm:ss": 46,
            "mmss.0": 47,
            "##0.0E+0": 48,
            "@": 49,
            "[$-404]e/m/d": 27,
            "m/d/yy": 30,
            "t0": 59,
            "t0.00": 60,
            "t#,##0": 61,
            "t#,##0.00": 62,
            "t0%": 67,
            "t0.00%": 68,
            "t# ?/?": 69,
            "t# ??/??": 70
        };

        function convertColor(color) {
            if (color.length < 6) {
                color = color.replace(/(\w)/g, function ($0, $1) {
                    return $1 + $1;
                });
            }

            color = color.substring(1).toUpperCase();

            if (color.length < 8) {
                color = "FF" + color;
            }

            return color;
        }

        var Workbook = kendo.Class.extend({
            init: function (options) {
                this.options = options || {};
                this._strings = {
                    indexes: {},
                    count: 0,
                    uniqueCount: 0
                };
                this._styles = [];

                this._sheets = $.map(this.options.sheets || [], $.proxy(function (options) {
                    return new Worksheet(options, this._strings, this._styles);
                }, this));
            },
            toDataURL: function () {
                if (typeof JSZip === "undefined") {
                    throw new Error("JSZip not found. Check http://docs.telerik.com/kendo-ui/framework/excel/introduction#requirements for more details.");
                }

                var zip = new JSZip();

                var docProps = zip.folder("docProps");

                docProps.file("core.xml", CORE({
                    creator: this.options.creator || "Kendo UI",
                    lastModifiedBy: this.options.creator || "Kendo UI",
                    created: this.options.date || new Date().toJSON(),
                    modified: this.options.date || new Date().toJSON()
                }));

                var sheetCount = this._sheets.length;

                docProps.file("app.xml", APP({ sheets: this._sheets }));

                var rels = zip.folder("_rels");
                rels.file(".rels", RELS);

                var xl = zip.folder("xl");

                var xlRels = xl.folder("_rels");
                xlRels.file("workbook.xml.rels", WORKBOOK_RELS({ count: sheetCount }));

                xl.file("workbook.xml", WORKBOOK({ sheets: this._sheets }));

                var worksheets = xl.folder("worksheets");

                for (var idx = 0; idx < sheetCount; idx++) {
                    worksheets.file(kendo.format("sheet{0}.xml", idx + 1), this._sheets[idx].toXML());
                }

                var styles = $.map(this._styles, $.parseJSON);

                var hasFont = function (style) {
                    return style.underline || style.bold || style.italic || style.color || style.fontName || style.fontSize;
                };

                var fonts = $.map(styles, function (style) {
                    if (style.color) {
                        style.color = convertColor(style.color);
                    }

                    if (hasFont(style)) {
                        return style;
                    }
                });

                var formats = $.map(styles, function (style) {
                    if (style.format && defaultFormats[style.format] === undefined) {
                        return style;
                    }
                });

                var fills = $.map(styles, function (style) {
                    if (style.background) {
                        style.background = convertColor(style.background);
                        return style;
                    }
                });

                xl.file("styles.xml", STYLES({
                    fonts: fonts,
                    fills: fills,
                    formats: formats,
                    styles: $.map(styles, function (style) {
                        var result = {};

                        if (hasFont(style)) {
                            result.fontId = $.inArray(style, fonts) + 1;
                        }

                        if (style.background) {
                            result.fillId = $.inArray(style, fills) + 2;
                        }

                        result.hAlign = style.hAlign;
                        result.vAlign = style.vAlign;

                        if (style.format) {
                            if (defaultFormats[style.format] !== undefined) {
                                result.numFmtId = defaultFormats[style.format];
                            } else {
                                result.numFmtId = 165 + $.inArray(style, formats);
                            }
                        }

                        return result;
                    })
                }));

                xl.file("sharedStrings.xml", SHARED_STRINGS(this._strings));

                zip.file("[Content_Types].xml", CONTENT_TYPES({ count: sheetCount }));

                return "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64," + zip.generate({ compression: "DEFLATE" });
            }
        });

        kendo.ooxml = {
            Workbook: Workbook,
            Worksheet: Worksheet
        };

    })(kendo);





    (function ($, kendo) {

        kendo.ExcelExporter = kendo.Class.extend({
            init: function (options) {
                options.columns = this._trimColumns(options.columns || []);

                this.columns = $.map(this._leafColumns(options.columns || []), this._prepareColumn);

                this.options = options;

                var dataSource = options.dataSource;

                if (dataSource instanceof kendo.data.DataSource) {
                    this.dataSource = new dataSource.constructor($.extend(
                {},
                dataSource.options,
                {
                    page: options.allPages ? 0 : dataSource.page(),
                    filter: dataSource.filter(),
                    pageSize: options.allPages ? dataSource.total() : dataSource.pageSize(),
                    sort: dataSource.sort(),
                    group: dataSource.group(),
                    aggregate: dataSource.aggregate()
                }));
                } else {
                    this.dataSource = kendo.data.DataSource.create(dataSource);
                }

                if (dataSource instanceof kendo.data.TreeListDataSource) {
                    this.dataSource.data(dataSource.view().toJSON());
                }
            },
            _trimColumns: function (columns) {
                var that = this;
                return $.grep(columns, function (column) {
                    var result = !(!column.field || column.hidden);
                    if (!result && column.columns) {
                        result = that._trimColumns(column.columns).length > 0;
                    }
                    return result;
                });
            },
            _leafColumns: function (columns) {
                var result = [];

                for (var idx = 0; idx < columns.length; idx++) {
                    if (!columns[idx].columns) {
                        result.push(columns[idx]);
                        continue;
                    }
                    result = result.concat(this._leafColumns(columns[idx].columns));
                }

                return result;
            },
            workbook: function () {
                var promise = this.dataSource.fetch();

                return promise.then($.proxy(function () {
                    return {
                        sheets: [{
                            columns: this._columns(),
                            rows: this._rows(),
                            freezePane: this._freezePane(),
                            filter: this._filter()
                        }]
                    };
                }, this));
            },
            _prepareColumn: function (column) {
                if (!column.field || column.hidden) {
                    return;
                }

                return $.extend({}, column, {
                    groupHeaderTemplate: kendo.template(column.groupHeaderTemplate || "${title}: ${value}"),
                    groupFooterTemplate: column.groupFooterTemplate ? kendo.template(column.groupFooterTemplate) : null,
                    footerTemplate: column.footerTemplate ? kendo.template(column.footerTemplate) : null
                });
            },
            _filter: function () {
                if (!this.options.filterable) {
                    return null;
                }

                var depth = this._depth();

                return {
                    from: depth,
                    to: depth + this.columns.length - 1
                };
            },
            _dataRows: function (dataItems, level) {
                var depth = this._depth();
                var rows = $.map(dataItems, $.proxy(function (dataItem) {
                    if (this._hierarchical()) {
                        level = this.dataSource.level(dataItem) + 1;
                    }

                    var cells = $.map(new Array(level), function () {
                        return {
                            background: "#dfdfdf",
                            color: "#333"
                        };
                    });

                    // grouped
                    if (depth && dataItem.items) {
                        var column = $.grep(this.columns, function (column) {
                            return column.field == dataItem.field;
                        })[0];

                        var title = column && column.title ? column.title : dataItem.field;
                        var template = column ? column.groupHeaderTemplate : null;
                        var value = title + ": " + dataItem.value;
                        var group = $.extend({
                            title: title,
                            field: dataItem.field,
                            value: dataItem.value,
                            aggregates: dataItem.aggregates
                        }, dataItem.aggregates[dataItem.field]);

                        if (template) {
                            value = template(group);
                        }

                        cells.push({
                            value: value,
                            background: "#dfdfdf",
                            color: "#333",
                            colSpan: this.columns.length + depth - level
                        });

                        var rows = this._dataRows(dataItem.items, level + 1);

                        rows.unshift({
                            type: "group-header",
                            cells: cells
                        });

                        return rows.concat(this._footer(dataItem, level + 1));
                    } else {
                        var dataCells = $.map(this.columns, $.proxy(this._cell, this, dataItem));

                        if (this._hierarchical()) {
                            dataCells[0].colSpan = depth - level + 1;
                        }

                        return {
                            type: "data",
                            cells: cells.concat(dataCells)
                        };
                    }
                }, this));

                return rows;
            },
            _footer: function (dataItem, level) {
                var rows = [];
                var footer = false;

                var cells = $.map(this.columns, function (column) {
                    if (column.groupFooterTemplate) {
                        footer = true;
                        return {
                            background: "#dfdfdf",
                            color: "#333",
                            value: column.groupFooterTemplate(dataItem.aggregates[column.field])
                        };
                    } else {
                        return {
                            background: "#dfdfdf",
                            color: "#333"
                        };
                    }
                });

                if (footer) {
                    rows.push({
                        type: "group-footer",
                        cells: $.map(new Array(level), function () {
                            return {
                                background: "#dfdfdf",
                                color: "#333"
                            };
                        }).concat(cells)
                    });
                }

                return rows;
            },
            _isColumnVisible: function (column) {
                return this._visibleColumns([column]).length > 0 && (column.field || column.columns);
            },
            _visibleColumns: function (columns) {
                var that = this;
                return $.grep(columns, function (column) {
                    var result = !column.hidden;
                    if (result && column.columns) {
                        result = that._visibleColumns(column.columns).length > 0;
                    }
                    return result;
                });
            },
            _headerRow: function (row, groups) {
                var headers = $.map(row.cells, function (cell) {
                    return {
                        background: "#7a7a7a",
                        color: "#fff",
                        value: cell.title,
                        colSpan: cell.colSpan > 1 ? cell.colSpan : 1,
                        rowSpan: row.rowSpan > 1 && !cell.colSpan ? row.rowSpan : 1
                    };
                });

                if (this._hierarchical()) {
                    headers[0].colSpan = this._depth() + 1;
                }

                return {
                    type: "header",
                    cells: $.map(new Array(groups.length), function () {
                        return {
                            background: "#7a7a7a",
                            color: "#fff"
                        };
                    }).concat(headers)
                };
            },
            _prependHeaderRows: function (rows) {
                var groups = this.dataSource.group();

                var headerRows = [{ rowSpan: 1, cells: [], index: 0}];

                this._prepareHeaderRows(headerRows, this.options.columns);

                for (var idx = headerRows.length - 1; idx >= 0; idx--) {
                    rows.unshift(this._headerRow(headerRows[idx], groups));
                }
            },
            _prepareHeaderRows: function (rows, columns, parentCell, parentRow) {
                var row = parentRow || rows[rows.length - 1];

                var childRow = rows[row.index + 1];
                var totalColSpan = 0;
                var column;
                var cell;

                for (var idx = 0; idx < columns.length; idx++) {
                    column = columns[idx];
                    if (this._isColumnVisible(column)) {

                        cell = { title: column.title || column.field, colSpan: 0 };
                        row.cells.push(cell);

                        if (column.columns && column.columns.length) {
                            if (!childRow) {
                                childRow = { rowSpan: 0, cells: [], index: rows.length };
                                rows.push(childRow);
                            }
                            cell.colSpan = this._trimColumns(this._visibleColumns(column.columns)).length;
                            this._prepareHeaderRows(rows, column.columns, cell, childRow);
                            totalColSpan += cell.colSpan - 1;
                            row.rowSpan = rows.length - row.index;
                        }
                    }
                }
                if (parentCell) {
                    parentCell.colSpan += totalColSpan;
                }
            },
            _rows: function () {
                var groups = this.dataSource.group();

                var rows = this._dataRows(this.dataSource.view(), 0);

                if (this.columns.length) {

                    this._prependHeaderRows(rows);

                    var footer = false;

                    var cells = $.map(this.columns, $.proxy(function (column) {
                        if (column.footerTemplate) {
                            footer = true;
                            return {
                                background: "#dfdfdf",
                                color: "#333",
                                value: column.footerTemplate(this.dataSource.aggregates()[column.field])
                            };
                        } else {
                            return {
                                background: "#dfdfdf",
                                color: "#333"
                            };
                        }
                    }, this));

                    if (footer) {
                        rows.push({
                            type: "footer",
                            cells: $.map(new Array(groups.length), function () {
                                return {
                                    background: "#dfdfdf",
                                    color: "#333"
                                };
                            }).concat(cells)
                        });
                    }
                }

                return rows;
            },
            _headerDepth: function (columns) {
                var result = 1;
                var max = 0;

                for (var idx = 0; idx < columns.length; idx++) {
                    if (columns[idx].columns) {
                        var temp = this._headerDepth(columns[idx].columns);
                        if (temp > max) {
                            max = temp;
                        }
                    }
                }
                return result + max;
            },
            _freezePane: function () {
                var columns = this._visibleColumns(this.options.columns || []);

                var colSplit = this._trimColumns(this._leafColumns($.grep(columns, function (column) {
                    return column.locked;
                }))).length;

                return {
                    rowSplit: this._headerDepth(columns),
                    colSplit: colSplit ? colSplit + this.dataSource.group().length : 0
                };
            },
            _cell: function (dataItem, column) {
                if (column.field) {
                    return {
                        value: dataItem.get(column.field)
                    };
                }
            },
            _hierarchical: function () {
                return this.options.hierarchy && this.dataSource.level;
            },
            _depth: function () {
                var dataSource = this.dataSource;
                var depth = 0;
                var view, i, level;

                if (this._hierarchical()) {
                    view = dataSource.view();

                    for (i = 0; i < view.length; i++) {
                        level = dataSource.level(view[i]);

                        if (level > depth) {
                            depth = level;
                        }
                    }

                    depth++;
                } else {
                    depth = dataSource.group().length;
                }

                return depth;
            },
            _columns: function () {
                var depth = this._depth();
                var columns = $.map(new Array(depth), function () {
                    return { width: 20 };
                });

                return columns.concat($.map(this.columns, function (column) {
                    return {
                        width: parseInt(column.width, 10),
                        autoWidth: column.width ? false : true
                    };
                }));
            }
        });

        kendo.ExcelMixin = {
            extend: function (proto) {
                proto.events.push("excelExport");
                proto.options.excel = $.extend(proto.options.excel, this.options);
                proto.saveAsExcel = this.saveAsExcel;
            },
            options: {
                proxyURL: "",
                allPages: false,
                filterable: false,
                fileName: "Export.xlsx"
            },
            saveAsExcel: function () {
                var excel = this.options.excel || {};

                var exporter = new kendo.ExcelExporter({
                    columns: this.columns,
                    dataSource: this.dataSource,
                    allPages: excel.allPages,
                    filterable: excel.filterable,
                    hierarchy: excel.hierarchy
                });

                exporter.workbook().then($.proxy(function (book) {
                    if (!this.trigger("excelExport", { workbook: book })) {
                        var workbook = new kendo.ooxml.Workbook(book);

                        kendo.saveAs({
                            dataURI: workbook.toDataURL(),
                            fileName: book.fileName || excel.fileName,
                            proxyURL: excel.proxyURL,
                            forceProxy: excel.forceProxy
                        });
                    }
                }, this));
            }
        };

    })(kendo.jQuery, kendo);





    (function () {
        kendo.data.transports.signalr = kendo.data.RemoteTransport.extend({
            init: function (options) {
                var signalr = options && options.signalr ? options.signalr : {};

                var promise = signalr.promise;

                if (!promise) {
                    throw new Error('The "promise" option must be set.');
                }

                if (typeof promise.done != "function" || typeof promise.fail != "function") {
                    throw new Error('The "promise" option must be a Promise.');
                }

                this.promise = promise;

                var hub = signalr.hub;

                if (!hub) {
                    throw new Error('The "hub" option must be set.');
                }

                if (typeof hub.on != "function" || typeof hub.invoke != "function") {
                    throw new Error('The "hub" option is not a valid SignalR hub proxy.');
                }

                this.hub = hub;

                kendo.data.RemoteTransport.fn.init.call(this, options);
            },

            push: function (callbacks) {
                var client = this.options.signalr.client || {};

                if (client.create) {
                    this.hub.on(client.create, callbacks.pushCreate);
                }

                if (client.update) {
                    this.hub.on(client.update, callbacks.pushUpdate);
                }

                if (client.destroy) {
                    this.hub.on(client.destroy, callbacks.pushDestroy);
                }
            },

            _crud: function (options, type) {
                var hub = this.hub;

                var server = this.options.signalr.server;

                if (!server || !server[type]) {
                    throw new Error(kendo.format('The "server.{0}" option must be set.', type));
                }

                var args = [server[type]];

                var data = this.parameterMap(options.data, type);

                if (!$.isEmptyObject(data)) {
                    args.push(data);
                }

                this.promise.done(function () {
                    hub.invoke.apply(hub, args)
                          .done(options.success)
                          .fail(options.error);
                });
            },

            read: function (options) {
                this._crud(options, "read");
            },

            create: function (options) {
                this._crud(options, "create");
            },

            update: function (options) {
                this._crud(options, "update");
            },

            destroy: function (options) {
                this._crud(options, "destroy");
            }
        });
    })();





    (function ($, parseFloat, parseInt) {
        var Color = function (value) {
            var color = this,
            formats = Color.formats,
            re,
            processor,
            parts,
            i,
            channels;

            if (arguments.length === 1) {
                value = color.resolveColor(value);

                for (i = 0; i < formats.length; i++) {
                    re = formats[i].re;
                    processor = formats[i].process;
                    parts = re.exec(value);

                    if (parts) {
                        channels = processor(parts);
                        color.r = channels[0];
                        color.g = channels[1];
                        color.b = channels[2];
                    }
                }
            } else {
                color.r = arguments[0];
                color.g = arguments[1];
                color.b = arguments[2];
            }

            color.r = color.normalizeByte(color.r);
            color.g = color.normalizeByte(color.g);
            color.b = color.normalizeByte(color.b);
        };

        Color.prototype = {
            toHex: function () {
                var color = this,
                pad = color.padDigit,
                r = color.r.toString(16),
                g = color.g.toString(16),
                b = color.b.toString(16);

                return "#" + pad(r) + pad(g) + pad(b);
            },

            resolveColor: function (value) {
                value = value || "black";

                if (value.charAt(0) == "#") {
                    value = value.substr(1, 6);
                }

                value = value.replace(/ /g, "");
                value = value.toLowerCase();
                value = Color.namedColors[value] || value;

                return value;
            },

            normalizeByte: function (value) {
                return (value < 0 || isNaN(value)) ? 0 : ((value > 255) ? 255 : value);
            },

            padDigit: function (value) {
                return (value.length === 1) ? "0" + value : value;
            },

            brightness: function (value) {
                var color = this,
                round = Math.round;

                color.r = round(color.normalizeByte(color.r * value));
                color.g = round(color.normalizeByte(color.g * value));
                color.b = round(color.normalizeByte(color.b * value));

                return color;
            },

            percBrightness: function () {
                var color = this;

                return Math.sqrt(0.241 * color.r * color.r + 0.691 * color.g * color.g + 0.068 * color.b * color.b);
            }
        };

        Color.formats = [{
            re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
            process: function (parts) {
                return [
                    parseInt(parts[1], 10), parseInt(parts[2], 10), parseInt(parts[3], 10)
                ];
            }
        }, {
            re: /^(\w{2})(\w{2})(\w{2})$/,
            process: function (parts) {
                return [
                    parseInt(parts[1], 16), parseInt(parts[2], 16), parseInt(parts[3], 16)
                ];
            }
        }, {
            re: /^(\w{1})(\w{1})(\w{1})$/,
            process: function (parts) {
                return [
                    parseInt(parts[1] + parts[1], 16),
                    parseInt(parts[2] + parts[2], 16),
                    parseInt(parts[3] + parts[3], 16)
                ];
            }
        }
    ];

        Color.namedColors = {
            aqua: "00ffff", azure: "f0ffff", beige: "f5f5dc",
            black: "000000", blue: "0000ff", brown: "a52a2a",
            coral: "ff7f50", cyan: "00ffff", darkblue: "00008b",
            darkcyan: "008b8b", darkgray: "a9a9a9", darkgreen: "006400",
            darkorange: "ff8c00", darkred: "8b0000", dimgray: "696969",
            fuchsia: "ff00ff", gold: "ffd700", goldenrod: "daa520",
            gray: "808080", green: "008000", greenyellow: "adff2f",
            indigo: "4b0082", ivory: "fffff0", khaki: "f0e68c",
            lightblue: "add8e6", lightgrey: "d3d3d3", lightgreen: "90ee90",
            lightpink: "ffb6c1", lightyellow: "ffffe0", lime: "00ff00",
            limegreen: "32cd32", linen: "faf0e6", magenta: "ff00ff",
            maroon: "800000", mediumblue: "0000cd", navy: "000080",
            olive: "808000", orange: "ffa500", orangered: "ff4500",
            orchid: "da70d6", pink: "ffc0cb", plum: "dda0dd",
            purple: "800080", red: "ff0000", royalblue: "4169e1",
            salmon: "fa8072", silver: "c0c0c0", skyblue: "87ceeb",
            slateblue: "6a5acd", slategray: "708090", snow: "fffafa",
            steelblue: "4682b4", tan: "d2b48c", teal: "008080",
            tomato: "ff6347", turquoise: "40e0d0", violet: "ee82ee",
            wheat: "f5deb3", white: "ffffff", whitesmoke: "f5f5f5",
            yellow: "ffff00", yellowgreen: "9acd32"
        };

        // Tools from ColorPicker =================================================

        var namedColorRegexp = ["transparent"];
        for (var i in Color.namedColors) {
            if (Color.namedColors.hasOwnProperty(i)) {
                namedColorRegexp.push(i);
            }
        }
        namedColorRegexp = new RegExp("^(" + namedColorRegexp.join("|") + ")(\\W|$)", "i");

        /*jshint eqnull:true  */

        function parseColor(color, nothrow) {
            var m, ret;
            if (color == null || color == "none") {
                return null;
            }
            if (color instanceof _Color) {
                return color;
            }
            color = color.toLowerCase();
            if ((m = namedColorRegexp.exec(color))) {
                if (m[1] == "transparent") {
                    color = new _RGB(1, 1, 1, 0);
                }
                else {
                    color = parseColor(Color.namedColors[m[1]], nothrow);
                }
                color.match = [m[1]];
                return color;
            }
            if ((m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(color))) {
                ret = new _Bytes(parseInt(m[1], 16),
                             parseInt(m[2], 16),
                             parseInt(m[3], 16), 1);
            }
            else if ((m = /^#?([0-9a-f])([0-9a-f])([0-9a-f])/i.exec(color))) {
                ret = new _Bytes(parseInt(m[1] + m[1], 16),
                             parseInt(m[2] + m[2], 16),
                             parseInt(m[3] + m[3], 16), 1);
            }
            else if ((m = /^rgb\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/.exec(color))) {
                ret = new _Bytes(parseInt(m[1], 10),
                             parseInt(m[2], 10),
                             parseInt(m[3], 10), 1);
            }
            else if ((m = /^rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9.]+)\s*\)/.exec(color))) {
                ret = new _Bytes(parseInt(m[1], 10),
                             parseInt(m[2], 10),
                             parseInt(m[3], 10), parseFloat(m[4]));
            }
            else if ((m = /^rgb\(\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9]*\.?[0-9]+)%\s*\)/.exec(color))) {
                ret = new _RGB(parseFloat(m[1]) / 100,
                           parseFloat(m[2]) / 100,
                           parseFloat(m[3]) / 100, 1);
            }
            else if ((m = /^rgba\(\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9.]+)\s*\)/.exec(color))) {
                ret = new _RGB(parseFloat(m[1]) / 100,
                           parseFloat(m[2]) / 100,
                           parseFloat(m[3]) / 100, parseFloat(m[4]));
            }
            if (ret) {
                ret.match = m;
            } else if (!nothrow) {
                throw new Error("Cannot parse color: " + color);
            }
            return ret;
        }

        function hex(n, width, pad) {
            if (!pad) { pad = "0"; }
            n = n.toString(16);
            while (width > n.length) {
                n = "0" + n;
            }
            return n;
        }

        var _Color = kendo.Class.extend({
            toHSV: function () { return this; },
            toRGB: function () { return this; },
            toHex: function () { return this.toBytes().toHex(); },
            toBytes: function () { return this; },
            toCss: function () { return "#" + this.toHex(); },
            toCssRgba: function () {
                var rgb = this.toBytes();
                return "rgba(" + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + parseFloat((+this.a).toFixed(3)) + ")";
            },
            toDisplay: function () {
                if (kendo.support.browser.msie && kendo.support.browser.version < 9) {
                    return this.toCss(); // no RGBA support; does it support any opacity in colors?
                }
                return this.toCssRgba();
            },
            equals: function (c) { return c === this || c !== null && this.toCssRgba() == parseColor(c).toCssRgba(); },
            diff: function (c2) {
                if (c2 == null) {
                    return NaN;
                }
                var c1 = this.toBytes();
                c2 = c2.toBytes();
                return Math.sqrt(Math.pow((c1.r - c2.r) * 0.30, 2) +
                             Math.pow((c1.g - c2.g) * 0.59, 2) +
                             Math.pow((c1.b - c2.b) * 0.11, 2));
            },
            clone: function () {
                var c = this.toBytes();
                if (c === this) {
                    c = new _Bytes(c.r, c.g, c.b, c.a);
                }
                return c;
            }
        });

        var _RGB = _Color.extend({
            init: function (r, g, b, a) {
                this.r = r; this.g = g; this.b = b; this.a = a;
            },
            toHSV: function () {
                var min, max, delta, h, s, v;
                var r = this.r, g = this.g, b = this.b;
                min = Math.min(r, g, b);
                max = Math.max(r, g, b);
                v = max;
                delta = max - min;
                if (delta === 0) {
                    return new _HSV(0, 0, v, this.a);
                }
                if (max !== 0) {
                    s = delta / max;
                    if (r == max) {
                        h = (g - b) / delta;
                    } else if (g == max) {
                        h = 2 + (b - r) / delta;
                    } else {
                        h = 4 + (r - g) / delta;
                    }
                    h *= 60;
                    if (h < 0) {
                        h += 360;
                    }
                } else {
                    s = 0;
                    h = -1;
                }
                return new _HSV(h, s, v, this.a);
            },
            toBytes: function () {
                return new _Bytes(this.r * 255, this.g * 255, this.b * 255, this.a);
            }
        });

        var _Bytes = _RGB.extend({
            init: function (r, g, b, a) {
                this.r = Math.round(r); this.g = Math.round(g); this.b = Math.round(b); this.a = a;
            },
            toRGB: function () {
                return new _RGB(this.r / 255, this.g / 255, this.b / 255, this.a);
            },
            toHSV: function () {
                return this.toRGB().toHSV();
            },
            toHex: function () {
                return hex(this.r, 2) + hex(this.g, 2) + hex(this.b, 2);
            },
            toBytes: function () {
                return this;
            }
        });

        var _HSV = _Color.extend({
            init: function (h, s, v, a) {
                this.h = h; this.s = s; this.v = v; this.a = a;
            },
            toRGB: function () {
                var h = this.h, s = this.s, v = this.v;
                var i, r, g, b, f, p, q, t;
                if (s === 0) {
                    r = g = b = v;
                } else {
                    h /= 60;
                    i = Math.floor(h);
                    f = h - i;
                    p = v * (1 - s);
                    q = v * (1 - s * f);
                    t = v * (1 - s * (1 - f));
                    switch (i) {
                        case 0: r = v; g = t; b = p; break;
                        case 1: r = q; g = v; b = p; break;
                        case 2: r = p; g = v; b = t; break;
                        case 3: r = p; g = q; b = v; break;
                        case 4: r = t; g = p; b = v; break;
                        default: r = v; g = p; b = q; break;
                    }
                }
                return new _RGB(r, g, b, this.a);
            },
            toBytes: function () {
                return this.toRGB().toBytes();
            }
        });

        Color.fromBytes = function (r, g, b, a) {
            return new _Bytes(r, g, b, a != null ? a : 1);
        };

        Color.fromRGB = function (r, g, b, a) {
            return new _RGB(r, g, b, a != null ? a : 1);
        };

        Color.fromHSV = function (h, s, v, a) {
            return new _HSV(h, s, v, a != null ? a : 1);
        };

        // Exports ================================================================
        kendo.Color = Color;
        kendo.parseColor = parseColor;

    })(window.kendo.jQuery, parseFloat, parseInt);

    (function ($) {
        // Imports ================================================================
        var math = Math,
        kendo = window.kendo,
        deepExtend = kendo.deepExtend,
        dataviz = kendo.dataviz;

        // Constants
        var DEG_TO_RAD = math.PI / 180,
        MAX_NUM = Number.MAX_VALUE,
        MIN_NUM = -Number.MAX_VALUE,
        UNDEFINED = "undefined",
        inArray = $.inArray,
        push = [].push,
        pop = [].pop,
        splice = [].splice,
        shift = [].shift,
        slice = [].slice,
        unshift = [].unshift;

        // Generic utility functions ==============================================
        function defined(value) {
            return typeof value !== UNDEFINED;
        }

        function round(value, precision) {
            var power = pow(precision);
            return math.round(value * power) / power;
        }

        // Extracted from round to get on the V8 "fast path"
        function pow(p) {
            if (p) {
                return math.pow(10, p);
            } else {
                return 1;
            }
        }

        function limitValue(value, min, max) {
            return math.max(math.min(value, max), min);
        }

        function rad(degrees) {
            return degrees * DEG_TO_RAD;
        }

        function deg(radians) {
            return radians / DEG_TO_RAD;
        }

        function isNumber(val) {
            return typeof val === "number" && !isNaN(val);
        }

        function valueOrDefault(value, defaultValue) {
            return defined(value) ? value : defaultValue;
        }

        function sqr(value) {
            return value * value;
        }

        function objectKey(object) {
            var parts = [];
            for (var key in object) {
                parts.push(key + object[key]);
            }

            return parts.sort().join("");
        }

        // Computes FNV-1 hash
        // See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
        function hashKey(str) {
            // 32-bit FNV-1 offset basis
            // See http://isthe.com/chongo/tech/comp/fnv/#FNV-param
            var hash = 0x811C9DC5;

            for (var i = 0; i < str.length; ++i) {
                hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
                hash ^= str.charCodeAt(i);
            }

            return hash >>> 0;
        }

        function hashObject(object) {
            return hashKey(objectKey(object));
        }

        var now = Date.now;
        if (!now) {
            now = function () {
                return new Date().getTime();
            }
        }

        // Array helpers ==========================================================
        function arrayLimits(arr) {
            var length = arr.length,
            i,
            min = MAX_NUM,
            max = MIN_NUM;

            for (i = 0; i < length; i++) {
                max = math.max(max, arr[i]);
                min = math.min(min, arr[i]);
            }

            return {
                min: min,
                max: max
            };
        }

        function arrayMin(arr) {
            return arrayLimits(arr).min;
        }

        function arrayMax(arr) {
            return arrayLimits(arr).max;
        }

        function sparseArrayMin(arr) {
            return sparseArrayLimits(arr).min;
        }

        function sparseArrayMax(arr) {
            return sparseArrayLimits(arr).max;
        }

        function sparseArrayLimits(arr) {
            var min = MAX_NUM,
            max = MIN_NUM;

            for (var i = 0, length = arr.length; i < length; i++) {
                var n = arr[i];
                if (n !== null && isFinite(n)) {
                    min = math.min(min, n);
                    max = math.max(max, n);
                }
            }

            return {
                min: min === MAX_NUM ? undefined : min,
                max: max === MIN_NUM ? undefined : max
            };
        }

        function last(array) {
            if (array) {
                return array[array.length - 1];
            }
        }

        function append(first, second) {
            first.push.apply(first, second);
            return first;
        }

        // Template helpers =======================================================
        function renderTemplate(text) {
            return kendo.template(text, { useWithBlock: false, paramName: "d" });
        }

        function renderAttr(name, value) {
            return (defined(value) && value !== null) ? " " + name + "='" + value + "' " : "";
        }

        function renderAllAttr(attrs) {
            var output = "";
            for (var i = 0; i < attrs.length; i++) {
                output += renderAttr(attrs[i][0], attrs[i][1]);
            }

            return output;
        }

        function renderStyle(attrs) {
            var output = "";
            for (var i = 0; i < attrs.length; i++) {
                var value = attrs[i][1];
                if (defined(value)) {
                    output += attrs[i][0] + ":" + value + ";";
                }
            }

            if (output !== "") {
                return output;
            }
        }

        function renderSize(size) {
            if (typeof size !== "string") {
                size += "px";
            }

            return size;
        }

        function renderPos(pos) {
            var result = [];

            if (pos) {
                var parts = kendo.toHyphens(pos).split("-");

                for (var i = 0; i < parts.length; i++) {
                    result.push("k-pos-" + parts[i]);
                }
            }

            return result.join(" ");
        }

        function isTransparent(color) {
            return color === "" || color === null || color === "none" || color === "transparent" || !defined(color);
        }

        // Exports ================================================================
        deepExtend(kendo, {
            util: {
                MAX_NUM: MAX_NUM,
                MIN_NUM: MIN_NUM,

                append: append,
                arrayLimits: arrayLimits,
                arrayMin: arrayMin,
                arrayMax: arrayMax,
                defined: defined,
                deg: deg,
                hashKey: hashKey,
                hashObject: hashObject,
                isNumber: isNumber,
                isTransparent: isTransparent,
                last: last,
                limitValue: limitValue,
                now: now,
                objectKey: objectKey,
                round: round,
                rad: rad,
                renderAttr: renderAttr,
                renderAllAttr: renderAllAttr,
                renderPos: renderPos,
                renderSize: renderSize,
                renderStyle: renderStyle,
                renderTemplate: renderTemplate,
                sparseArrayLimits: sparseArrayLimits,
                sparseArrayMin: sparseArrayMin,
                sparseArrayMax: sparseArrayMax,
                sqr: sqr,
                valueOrDefault: valueOrDefault
            }
        });

        kendo.dataviz.util = kendo.util;

    })(window.kendo.jQuery);



    (function ($) {
        // Imports ================================================================
        var kendo = window.kendo,
        deepExtend = kendo.deepExtend,
        fromCharCode = String.fromCharCode;

        // Constants
        var KEY_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

        // Generic utility functions ==============================================
        function encodeBase64(input) {
            var output = "";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i = 0;

            input = encodeUTF8(input);

            while (i < input.length) {

                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);

                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;

                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                }
                else if (isNaN(chr3)) {
                    enc4 = 64;
                }

                output = output +
                KEY_STR.charAt(enc1) + KEY_STR.charAt(enc2) +
                KEY_STR.charAt(enc3) + KEY_STR.charAt(enc4);
            }

            return output;
        }

        function encodeUTF8(input) {
            input = input.replace(/\r\n/g, "\n");
            var output = "";

            for (var i = 0; i < input.length; i++) {
                var c = input.charCodeAt(i);

                if (c < 0x80) {
                    // One byte
                    output += fromCharCode(c);
                }
                else if (c < 0x800) {
                    // Two bytes
                    output += fromCharCode(0xC0 | (c >>> 6));
                    output += fromCharCode(0x80 | (c & 0x3f));
                }
                else if (c < 0x10000) {
                    // Three bytes
                    output += fromCharCode(0xE0 | (c >>> 12));
                    output += fromCharCode(0x80 | (c >>> 6 & 0x3f));
                    output += fromCharCode(0x80 | (c & 0x3f));
                }
            }

            return output;
        }

        // Exports ================================================================
        deepExtend(kendo.util, {
            encodeBase64: encodeBase64,
            encodeUTF8: encodeUTF8
        });

    })(window.kendo.jQuery);



    (function ($) {
        // Imports ================================================================
        var math = Math,
        kendo = window.kendo,
        deepExtend = kendo.deepExtend,
        inArray = $.inArray;

        // Mixins =================================================================
        var ObserversMixin = {
            observers: function () {
                this._observers = this._observers || [];
                return this._observers;
            },

            addObserver: function (element) {
                if (!this._observers) {
                    this._observers = [element];
                } else {
                    this._observers.push(element);
                }
                return this;
            },

            removeObserver: function (element) {
                var observers = this.observers();
                var index = inArray(element, observers);
                if (index != -1) {
                    observers.splice(index, 1);
                }
                return this;
            },

            trigger: function (methodName, event) {
                var observers = this._observers;
                var observer;
                var idx;

                if (observers && !this._suspended) {
                    for (idx = 0; idx < observers.length; idx++) {
                        observer = observers[idx];
                        if (observer[methodName]) {
                            observer[methodName](event);
                        }
                    }
                }
                return this;
            },

            optionsChange: function (e) {
                this.trigger("optionsChange", e);
            },

            geometryChange: function (e) {
                this.trigger("geometryChange", e);
            },

            suspend: function () {
                this._suspended = (this._suspended || 0) + 1;
                return this;
            },

            resume: function () {
                this._suspended = math.max((this._suspended || 0) - 1, 0);
                return this;
            },

            _observerField: function (field, value) {
                if (this[field]) {
                    this[field].removeObserver(this);
                }
                this[field] = value;
                value.addObserver(this);
            }
        };

        // Exports ================================================================
        deepExtend(kendo, {
            mixins: {
                ObserversMixin: ObserversMixin
            }
        });

    })(window.kendo.jQuery);



    (function ($) {
        // Imports ================================================================
        var math = Math,
        pow = math.pow,
        inArray = $.inArray,

        kendo = window.kendo,
        Class = kendo.Class,
        deepExtend = kendo.deepExtend,
        ObserversMixin = kendo.mixins.ObserversMixin,

        util = kendo.util,
        defined = util.defined,
        rad = util.rad,
        deg = util.deg,
        round = util.round;

        var PI_DIV_2 = math.PI / 2,
        MIN_NUM = util.MIN_NUM,
        MAX_NUM = util.MAX_NUM;

        // Geometrical primitives =================================================
        var Point = Class.extend({
            init: function (x, y) {
                this.x = x || 0;
                this.y = y || 0;
            },

            equals: function (other) {
                return other && other.x === this.x && other.y === this.y;
            },

            clone: function () {
                return new Point(this.x, this.y);
            },

            rotate: function (angle, origin) {
                return this.transform(
                transform().rotate(angle, origin)
            );
            },

            translate: function (x, y) {
                this.x += x;
                this.y += y;

                this.geometryChange();

                return this;
            },

            translateWith: function (point) {
                return this.translate(point.x, point.y);
            },

            move: function (x, y) {
                this.x = this.y = 0;
                return this.translate(x, y);
            },

            scale: function (scaleX, scaleY) {
                if (!defined(scaleY)) {
                    scaleY = scaleX;
                }

                this.x *= scaleX;
                this.y *= scaleY;

                this.geometryChange();

                return this;
            },

            scaleCopy: function (scaleX, scaleY) {
                return this.clone().scale(scaleX, scaleY);
            },

            transform: function (transformation) {
                var mx = toMatrix(transformation),
                x = this.x,
                y = this.y;

                this.x = mx.a * x + mx.c * y + mx.e;
                this.y = mx.b * x + mx.d * y + mx.f;

                this.geometryChange();

                return this;
            },

            transformCopy: function (transformation) {
                var point = this.clone();

                if (transformation) {
                    point.transform(transformation);
                }

                return point;
            },

            distanceTo: function (point) {
                var dx = this.x - point.x;
                var dy = this.y - point.y;

                return math.sqrt(dx * dx + dy * dy);
            },

            round: function (digits) {
                this.x = round(this.x, digits);
                this.y = round(this.y, digits);

                this.geometryChange();

                return this;
            },

            toArray: function (digits) {
                var doRound = defined(digits);
                var x = doRound ? round(this.x, digits) : this.x;
                var y = doRound ? round(this.y, digits) : this.y;

                return [x, y];
            }
        });
        defineAccessors(Point.fn, ["x", "y"]);
        deepExtend(Point.fn, ObserversMixin);

        // IE < 9 doesn't allow to override toString on definition
        Point.fn.toString = function (digits, separator) {
            var x = this.x,
            y = this.y;

            if (defined(digits)) {
                x = round(x, digits);
                y = round(y, digits);
            }

            separator = separator || " ";
            return x + separator + y;
        };

        Point.create = function (arg0, arg1) {
            if (defined(arg0)) {
                if (arg0 instanceof Point) {
                    return arg0;
                } else if (arguments.length === 1 && arg0.length === 2) {
                    return new Point(arg0[0], arg0[1]);
                } else {
                    return new Point(arg0, arg1);
                }
            }
        };

        Point.min = function () {
            var minX = util.MAX_NUM;
            var minY = util.MAX_NUM;

            for (var i = 0; i < arguments.length; i++) {
                var pt = arguments[i];
                minX = math.min(pt.x, minX);
                minY = math.min(pt.y, minY);
            }

            return new Point(minX, minY);
        };

        Point.max = function (p0, p1) {
            var maxX = util.MIN_NUM;
            var maxY = util.MIN_NUM;

            for (var i = 0; i < arguments.length; i++) {
                var pt = arguments[i];
                maxX = math.max(pt.x, maxX);
                maxY = math.max(pt.y, maxY);
            }

            return new Point(maxX, maxY);
        };

        Point.minPoint = function () {
            return new Point(MIN_NUM, MIN_NUM);
        };

        Point.maxPoint = function () {
            return new Point(MAX_NUM, MAX_NUM);
        };

        Point.ZERO = new Point(0, 0);

        var Size = Class.extend({
            init: function (width, height) {
                this.width = width || 0;
                this.height = height || 0;
            },

            equals: function (other) {
                return other && other.width === this.width && other.height === this.height;
            },

            clone: function () {
                return new Size(this.width, this.height);
            },

            toArray: function (digits) {
                var doRound = defined(digits);
                var width = doRound ? round(this.width, digits) : this.width;
                var height = doRound ? round(this.height, digits) : this.height;

                return [width, height];
            }
        });
        defineAccessors(Size.fn, ["width", "height"]);
        deepExtend(Size.fn, ObserversMixin);

        Size.create = function (arg0, arg1) {
            if (defined(arg0)) {
                if (arg0 instanceof Size) {
                    return arg0;
                } else if (arguments.length === 1 && arg0.length === 2) {
                    return new Size(arg0[0], arg0[1]);
                } else {
                    return new Size(arg0, arg1);
                }
            }
        };

        Size.ZERO = new Size(0, 0);

        var Rect = Class.extend({
            init: function (origin, size) {
                this.setOrigin(origin || new Point());
                this.setSize(size || new Size());
            },

            clone: function () {
                return new Rect(
                this.origin.clone(),
                this.size.clone()
            );
            },

            equals: function (other) {
                return other &&
                   other.origin.equals(this.origin) &&
                   other.size.equals(this.size);
            },

            setOrigin: function (value) {
                this._observerField("origin", Point.create(value));
                this.geometryChange();
                return this;
            },

            getOrigin: function () {
                return this.origin;
            },

            setSize: function (value) {
                this._observerField("size", Size.create(value));
                this.geometryChange();
                return this;
            },

            getSize: function () {
                return this.size;
            },

            width: function () {
                return this.size.width;
            },

            height: function () {
                return this.size.height;
            },

            topLeft: function () {
                return this.origin.clone();
            },

            bottomRight: function () {
                return this.origin.clone().translate(this.width(), this.height());
            },

            topRight: function () {
                return this.origin.clone().translate(this.width(), 0);
            },

            bottomLeft: function () {
                return this.origin.clone().translate(0, this.height());
            },

            center: function () {
                return this.origin.clone().translate(this.width() / 2, this.height() / 2);
            },

            bbox: function (matrix) {
                var tl = this.topLeft().transformCopy(matrix);
                var tr = this.topRight().transformCopy(matrix);
                var br = this.bottomRight().transformCopy(matrix);
                var bl = this.bottomLeft().transformCopy(matrix);

                return Rect.fromPoints(tl, tr, br, bl);
            }
        });

        deepExtend(Rect.fn, ObserversMixin);

        Rect.fromPoints = function () {
            var topLeft = Point.min.apply(this, arguments);
            var bottomRight = Point.max.apply(this, arguments);
            var size = new Size(
            bottomRight.x - topLeft.x,
            bottomRight.y - topLeft.y
        );

            return new Rect(topLeft, size);
        };

        Rect.union = function (a, b) {
            return Rect.fromPoints(
            Point.min(a.topLeft(), b.topLeft()),
            Point.max(a.bottomRight(), b.bottomRight())
        );
        };

        Rect.intersect = function (a, b) {
            a = { left: a.topLeft().x,
                top: a.topLeft().y,
                right: a.bottomRight().x,
                bottom: a.bottomRight().y
            };

            b = { left: b.topLeft().x,
                top: b.topLeft().y,
                right: b.bottomRight().x,
                bottom: b.bottomRight().y
            };

            if (a.left <= b.right &&
            b.left <= a.right &&
            a.top <= b.bottom &&
            b.top <= a.bottom) {
                return Rect.fromPoints(
                new Point(math.max(a.left, b.left), math.max(a.top, b.top)),
                new Point(math.min(a.right, b.right), math.min(a.bottom, b.bottom))
            );
            }
        };

        var Circle = Class.extend({
            init: function (center, radius) {
                this.setCenter(center || new Point());
                this.setRadius(radius || 0);
            },

            setCenter: function (value) {
                this._observerField("center", Point.create(value));
                this.geometryChange();
                return this;
            },

            getCenter: function () {
                return this.center;
            },

            equals: function (other) {
                return other &&
                    other.center.equals(this.center) &&
                    other.radius === this.radius;
            },

            clone: function () {
                return new Circle(this.center.clone(), this.radius);
            },

            pointAt: function (angle) {
                return this._pointAt(rad(angle));
            },

            bbox: function (matrix) {
                var minPoint = Point.maxPoint();
                var maxPoint = Point.minPoint();
                var extremeAngles = ellipseExtremeAngles(this.center, this.radius, this.radius, matrix);

                for (var i = 0; i < 4; i++) {
                    var currentPointX = this._pointAt(extremeAngles.x + i * PI_DIV_2).transformCopy(matrix);
                    var currentPointY = this._pointAt(extremeAngles.y + i * PI_DIV_2).transformCopy(matrix);
                    var currentPoint = new Point(currentPointX.x, currentPointY.y);

                    minPoint = Point.min(minPoint, currentPoint);
                    maxPoint = Point.max(maxPoint, currentPoint);
                }

                // TODO: Let fromPoints figure out the min/max
                return Rect.fromPoints(minPoint, maxPoint);
            },

            _pointAt: function (angle) {
                var c = this.center;
                var r = this.radius;

                return new Point(
                c.x - r * math.cos(angle),
                c.y - r * math.sin(angle)
            );
            }
        });
        defineAccessors(Circle.fn, ["radius"]);
        deepExtend(Circle.fn, ObserversMixin);

        var Arc = Class.extend({
            init: function (center, options) {
                this.setCenter(center || new Point());

                options = options || {};
                this.radiusX = options.radiusX;
                this.radiusY = options.radiusY || options.radiusX;
                this.startAngle = options.startAngle;
                this.endAngle = options.endAngle;
                this.anticlockwise = options.anticlockwise || false;
            },

            // TODO: clone, equals
            clone: function () {
                return new Arc(this.center, {
                    radiusX: this.radiusX,
                    radiusY: this.radiusY,
                    startAngle: this.startAngle,
                    endAngle: this.endAngle,
                    anticlockwise: this.anticlockwise
                });
            },

            setCenter: function (value) {
                this._observerField("center", Point.create(value));
                this.geometryChange();
                return this;
            },

            getCenter: function () {
                return this.center;
            },

            MAX_INTERVAL: 45,

            pointAt: function (angle) {
                var center = this.center;
                var radian = rad(angle);

                return new Point(
                center.x + this.radiusX * math.cos(radian),
                center.y + this.radiusY * math.sin(radian)
            );
            },

            // TODO: Review, document
            curvePoints: function () {
                var startAngle = this.startAngle;
                var endAngle = this.endAngle;
                var dir = this.anticlockwise ? -1 : 1;
                var curvePoints = [this.pointAt(startAngle)];
                var currentAngle = startAngle;
                var interval = this._arcInterval();
                var intervalAngle = interval.endAngle - interval.startAngle;
                var subIntervalsCount = math.ceil(intervalAngle / this.MAX_INTERVAL);
                var subIntervalAngle = intervalAngle / subIntervalsCount;

                for (var i = 1; i <= subIntervalsCount; i++) {
                    var nextAngle = currentAngle + dir * subIntervalAngle;
                    var points = this._intervalCurvePoints(currentAngle, nextAngle);

                    curvePoints.push(points.cp1, points.cp2, points.p2);
                    currentAngle = nextAngle;
                }

                return curvePoints;
            },

            bbox: function (matrix) {
                var arc = this;
                var interval = arc._arcInterval();
                var startAngle = interval.startAngle;
                var endAngle = interval.endAngle;
                var extremeAngles = ellipseExtremeAngles(this.center, this.radiusX, this.radiusY, matrix);
                var extremeX = deg(extremeAngles.x);
                var extremeY = deg(extremeAngles.y);
                var currentPoint = arc.pointAt(startAngle).transformCopy(matrix);
                var endPoint = arc.pointAt(endAngle).transformCopy(matrix);
                var minPoint = Point.min(currentPoint, endPoint);
                var maxPoint = Point.max(currentPoint, endPoint);
                var currentAngleX = bboxStartAngle(extremeX, startAngle);
                var currentAngleY = bboxStartAngle(extremeY, startAngle);

                while (currentAngleX < endAngle || currentAngleY < endAngle) {
                    var currentPointX;
                    if (currentAngleX < endAngle) {
                        currentPointX = arc.pointAt(currentAngleX).transformCopy(matrix);
                        currentAngleX += 90;
                    }

                    var currentPointY;
                    if (currentAngleY < endAngle) {
                        currentPointY = arc.pointAt(currentAngleY).transformCopy(matrix);
                        currentAngleY += 90;
                    }

                    currentPoint = new Point(currentPointX.x, currentPointY.y);
                    minPoint = Point.min(minPoint, currentPoint);
                    maxPoint = Point.max(maxPoint, currentPoint);
                }

                // TODO: Let fromPoints figure out the min/max
                return Rect.fromPoints(minPoint, maxPoint);
            },

            _arcInterval: function () {
                var startAngle = this.startAngle;
                var endAngle = this.endAngle;
                var anticlockwise = this.anticlockwise;

                if (anticlockwise) {
                    var oldStart = startAngle;
                    startAngle = endAngle;
                    endAngle = oldStart;
                }

                if (startAngle > endAngle || (anticlockwise && startAngle === endAngle)) {
                    endAngle += 360;
                }

                return {
                    startAngle: startAngle,
                    endAngle: endAngle
                };
            },

            _intervalCurvePoints: function (startAngle, endAngle) {
                var arc = this;
                var p1 = arc.pointAt(startAngle);
                var p2 = arc.pointAt(endAngle);
                var p1Derivative = arc._derivativeAt(startAngle);
                var p2Derivative = arc._derivativeAt(endAngle);
                var t = (rad(endAngle) - rad(startAngle)) / 3;
                var cp1 = new Point(p1.x + t * p1Derivative.x, p1.y + t * p1Derivative.y);
                var cp2 = new Point(p2.x - t * p2Derivative.x, p2.y - t * p2Derivative.y);

                return {
                    p1: p1,
                    cp1: cp1,
                    cp2: cp2,
                    p2: p2
                };
            },

            _derivativeAt: function (angle) {
                var arc = this;
                var radian = rad(angle);

                return new Point(-arc.radiusX * math.sin(radian), arc.radiusY * math.cos(radian));
            }
        });
        defineAccessors(Arc.fn, ["radiusX", "radiusY", "startAngle", "endAngle", "anticlockwise"]);
        deepExtend(Arc.fn, ObserversMixin);

        Arc.fromPoints = function (start, end, rx, ry, largeArc, swipe) {
            var arcParameters = normalizeArcParameters(start.x, start.y, end.x, end.y, rx, ry, largeArc, swipe);
            return new Arc(arcParameters.center, {
                startAngle: arcParameters.startAngle,
                endAngle: arcParameters.endAngle,
                radiusX: rx,
                radiusY: ry,
                anticlockwise: swipe === 0
            });
        };

        var Matrix = Class.extend({
            init: function (a, b, c, d, e, f) {
                this.a = a || 0;
                this.b = b || 0;
                this.c = c || 0;
                this.d = d || 0;
                this.e = e || 0;
                this.f = f || 0;
            },

            multiplyCopy: function (m) {
                return new Matrix(
                this.a * m.a + this.c * m.b,
                this.b * m.a + this.d * m.b,
                this.a * m.c + this.c * m.d,
                this.b * m.c + this.d * m.d,
                this.a * m.e + this.c * m.f + this.e,
                this.b * m.e + this.d * m.f + this.f
            );
            },

            clone: function () {
                return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
            },

            equals: function (other) {
                if (!other) {
                    return false;
                }

                return this.a === other.a && this.b === other.b &&
                   this.c === other.c && this.d === other.d &&
                   this.e === other.e && this.f === other.f;
            },

            round: function (precision) {
                this.a = round(this.a, precision);
                this.b = round(this.b, precision);
                this.c = round(this.c, precision);
                this.d = round(this.d, precision);
                this.e = round(this.e, precision);
                this.f = round(this.f, precision);

                return this;
            },

            toArray: function (precision) {
                var arr = [this.a, this.b, this.c, this.d, this.e, this.f];

                if (defined(precision)) {
                    for (var i = 0; i < arr.length; i++) {
                        arr[i] = round(arr[i], precision);
                    }
                }

                return arr;
            }
        });

        Matrix.fn.toString = function (precision, separator) {
            return this.toArray(precision).join(separator || ",");
        };

        Matrix.translate = function (x, y) {
            return new Matrix(1, 0, 0, 1, x, y);
        };

        Matrix.unit = function () {
            return new Matrix(1, 0, 0, 1, 0, 0);
        };

        Matrix.rotate = function (angle, x, y) {
            var m = new Matrix();
            m.a = math.cos(rad(angle));
            m.b = math.sin(rad(angle));
            m.c = -m.b;
            m.d = m.a;
            m.e = (x - x * m.a + y * m.b) || 0;
            m.f = (y - y * m.a - x * m.b) || 0;

            return m;
        };

        Matrix.scale = function (scaleX, scaleY) {
            return new Matrix(scaleX, 0, 0, scaleY, 0, 0);
        };

        Matrix.IDENTITY = Matrix.unit();

        var Transformation = Class.extend({
            init: function (matrix) {
                this._matrix = matrix || Matrix.unit();
            },

            clone: function () {
                return new Transformation(
                this._matrix.clone()
            );
            },

            equals: function (other) {
                return other &&
                   other._matrix.equals(this._matrix);
            },

            _optionsChange: function () {
                this.optionsChange({
                    field: "transform",
                    value: this
                });
            },

            translate: function (x, y) {
                this._matrix = this._matrix.multiplyCopy(Matrix.translate(x, y));

                this._optionsChange();
                return this;
            },

            scale: function (scaleX, scaleY, origin) {
                if (!defined(scaleY)) {
                    scaleY = scaleX;
                }

                if (origin) {
                    origin = Point.create(origin);
                    this._matrix = this._matrix.multiplyCopy(Matrix.translate(origin.x, origin.y));
                }

                this._matrix = this._matrix.multiplyCopy(Matrix.scale(scaleX, scaleY));

                if (origin) {
                    this._matrix = this._matrix.multiplyCopy(Matrix.translate(-origin.x, -origin.y));
                }

                this._optionsChange();
                return this;
            },

            rotate: function (angle, origin) {
                origin = Point.create(origin) || Point.ZERO;

                this._matrix = this._matrix.multiplyCopy(Matrix.rotate(angle, origin.x, origin.y));

                this._optionsChange();
                return this;
            },

            multiply: function (transformation) {
                var matrix = toMatrix(transformation);

                this._matrix = this._matrix.multiplyCopy(matrix);

                this._optionsChange();
                return this;
            },

            matrix: function () {
                return this._matrix;
            }
        });

        deepExtend(Transformation.fn, ObserversMixin);

        function transform(matrix) {
            if (matrix === null) {
                return null;
            }

            if (matrix instanceof Transformation) {
                return matrix;
            }

            return new Transformation(matrix);
        }

        function toMatrix(value) {
            if (value && kendo.isFunction(value.matrix)) {
                return value.matrix();
            }

            return value;
        }

        // Helper functions =======================================================
        function ellipseExtremeAngles(center, rx, ry, matrix) {
            var extremeX = 0,
            extremeY = 0;

            if (matrix) {
                extremeX = math.atan2(matrix.c * ry, matrix.a * rx);
                if (matrix.b !== 0) {
                    extremeY = math.atan2(matrix.d * ry, matrix.b * rx);
                }
            }

            return {
                x: extremeX,
                y: extremeY
            };
        }

        function bboxStartAngle(angle, start) {
            while (angle < start) {
                angle += 90;
            }

            return angle;
        }

        function defineAccessors(fn, fields) {
            for (var i = 0; i < fields.length; i++) {
                var name = fields[i];
                var capitalized = name.charAt(0).toUpperCase() +
                              name.substring(1, name.length);

                fn["set" + capitalized] = setAccessor(name);
                fn["get" + capitalized] = getAccessor(name);
            }
        }

        function setAccessor(field) {
            return function (value) {
                if (this[field] !== value) {
                    this[field] = value;
                    this.geometryChange();
                }

                return this;
            };
        }

        function getAccessor(field) {
            return function () {
                return this[field];
            };
        }


        function elipseAngle(start, end, swipe) {
            if (start > end) {
                end += 360;
            }

            var alpha = math.abs(end - start);
            if (!swipe) {
                alpha = 360 - alpha;
            }

            return alpha;
        }

        function calculateAngle(cx, cy, rx, ry, x, y) {
            var cos = round((x - cx) / rx, 3);
            var sin = round((y - cy) / ry, 3);

            return round(deg(math.atan2(sin, cos)));
        }

        function normalizeArcParameters(x1, y1, x2, y2, rx, ry, largeArc, swipe) {
            var cx, cy;
            var cx1, cy1;
            var a, b, c, sqrt;

            if (y1 !== y2) {
                var x21 = x2 - x1;
                var y21 = y2 - y1;
                var rx2 = pow(rx, 2), ry2 = pow(ry, 2);
                var k = (ry2 * x21 * (x1 + x2) + rx2 * y21 * (y1 + y2)) / (2 * rx2 * y21);
                var yk2 = k - y2;
                var l = -(x21 * ry2) / (rx2 * y21);

                a = 1 / rx2 + pow(l, 2) / ry2;
                b = 2 * ((l * yk2) / ry2 - x2 / rx2);
                c = pow(x2, 2) / rx2 + pow(yk2, 2) / ry2 - 1;
                sqrt = math.sqrt(pow(b, 2) - 4 * a * c);

                cx = (-b - sqrt) / (2 * a);
                cy = k + l * cx;
                cx1 = (-b + sqrt) / (2 * a);
                cy1 = k + l * cx1;
            } else if (x1 !== x2) {
                b = -2 * y2;
                c = pow(((x2 - x1) * ry) / (2 * rx), 2) + pow(y2, 2) - pow(ry, 2);
                sqrt = math.sqrt(pow(b, 2) - 4 * c);

                cx = cx1 = (x1 + x2) / 2;
                cy = (-b - sqrt) / 2;
                cy1 = (-b + sqrt) / 2;
            } else {
                return false;
            }

            var start = calculateAngle(cx, cy, rx, ry, x1, y1);
            var end = calculateAngle(cx, cy, rx, ry, x2, y2);
            var alpha = elipseAngle(start, end, swipe);

            if ((largeArc && alpha <= 180) || (!largeArc && alpha > 180)) {
                cx = cx1; cy = cy1;
                start = calculateAngle(cx, cy, rx, ry, x1, y1);
                end = calculateAngle(cx, cy, rx, ry, x2, y2);
            }

            return {
                center: new Point(cx, cy),
                startAngle: start,
                endAngle: end
            };
        }


        // Exports ================================================================
        deepExtend(kendo, {
            geometry: {
                Arc: Arc,
                Circle: Circle,
                Matrix: Matrix,
                Point: Point,
                Rect: Rect,
                Size: Size,
                Transformation: Transformation,
                transform: transform,
                toMatrix: toMatrix
            }
        });

        kendo.dataviz.geometry = kendo.geometry;

    })(window.kendo.jQuery);



    (function ($) {

        // Imports ================================================================
        var doc = document,
        noop = $.noop,
        toString = Object.prototype.toString,

        kendo = window.kendo,
        Class = kendo.Class,
        Widget = kendo.ui.Widget,
        deepExtend = kendo.deepExtend,

        util = kendo.util,
        defined = util.defined;

        // Base drawing surface ==================================================
        var Surface = kendo.Observable.extend({
            init: function (element, options) {
                kendo.Observable.fn.init.call(this);

                this.options = deepExtend({}, this.options, options);
                this.bind(this.events, this.options);

                this._click = this._handler("click");
                this._mouseenter = this._handler("mouseenter");
                this._mouseleave = this._handler("mouseleave");

                this.element = $(element);

                if (this.options.width) {
                    this.element.css("width", this.options.width);
                }

                if (this.options.height) {
                    this.element.css("height", this.options.height);
                }
            },

            options: {},

            events: [
            "click",
            "mouseenter",
            "mouseleave",
            "resize"
        ],

            draw: noop,
            clear: noop,
            destroy: noop,

            resize: Widget.fn.resize,
            size: Widget.fn.size,

            getSize: function () {
                return {
                    width: this.element.width(),
                    height: this.element.height()
                };
            },

            setSize: function (size) {
                this.element.css({
                    width: size.width,
                    height: size.height
                });

                this._size = size;
                this._resize();
            },

            eventTarget: function (e) {
                var domNode = $(e.touch ? e.touch.initialTouch : e.target);
                var node;

                while (!node && domNode.length > 0) {
                    node = domNode[0]._kendoNode;
                    if (domNode.is(this.element) || domNode.length === 0) {
                        break;
                    }

                    domNode = domNode.parent();
                }

                if (node) {
                    return node.srcElement;
                }
            },

            _resize: noop,

            _handler: function (event) {
                var surface = this;

                return function (e) {
                    var node = surface.eventTarget(e);
                    if (node) {
                        surface.trigger(event, {
                            element: node,
                            originalEvent: e
                        });
                    }
                };
            }
        });

        Surface.create = function (element, options) {
            return SurfaceFactory.current.create(element, options);
        };

        // Base surface node =====================================================
        var BaseNode = Class.extend({
            init: function (srcElement) {
                this.childNodes = [];
                this.parent = null;

                if (srcElement) {
                    this.srcElement = srcElement;
                    this.observe();
                }
            },

            destroy: function () {
                if (this.srcElement) {
                    this.srcElement.removeObserver(this);
                }

                var children = this.childNodes;
                for (var i = 0; i < children.length; i++) {
                    this.childNodes[i].destroy();
                }

                this.parent = null;
            },

            load: noop,

            observe: function () {
                if (this.srcElement) {
                    this.srcElement.addObserver(this);
                }
            },

            append: function (node) {
                this.childNodes.push(node);
                node.parent = this;
            },

            insertAt: function (node, pos) {
                this.childNodes.splice(pos, 0, node);
                node.parent = this;
            },

            remove: function (index, count) {
                var end = index + count;
                for (var i = index; i < end; i++) {
                    this.childNodes[i].removeSelf();
                }
                this.childNodes.splice(index, count);
            },

            removeSelf: function () {
                this.clear();
                this.destroy();
            },

            clear: function () {
                this.remove(0, this.childNodes.length);
            },

            invalidate: function () {
                if (this.parent) {
                    this.parent.invalidate();
                }
            },

            geometryChange: function () {
                this.invalidate();
            },

            optionsChange: function () {
                this.invalidate();
            },

            childrenChange: function (e) {
                if (e.action === "add") {
                    this.load(e.items, e.index);
                } else if (e.action === "remove") {
                    this.remove(e.index, e.items.length);
                }

                this.invalidate();
            }
        });

        // Options storage with optional observer =============================
        var OptionsStore = Class.extend({
            init: function (options, prefix) {
                var field,
                member;

                this.prefix = prefix || "";

                for (field in options) {
                    member = options[field];
                    member = this._wrap(member, field);
                    this[field] = member;
                }
            },

            get: function (field) {
                return kendo.getter(field, true)(this);
            },

            set: function (field, value) {
                var current = kendo.getter(field, true)(this);

                if (current !== value) {
                    var composite = this._set(field, this._wrap(value, field));
                    if (!composite) {
                        this.optionsChange({
                            field: this.prefix + field,
                            value: value
                        });
                    }
                }
            },

            _set: function (field, value) {
                var composite = field.indexOf(".") >= 0;

                if (composite) {
                    var parts = field.split("."),
                    path = "",
                    obj;

                    while (parts.length > 1) {
                        path += parts.shift();
                        obj = kendo.getter(path, true)(this);

                        if (!obj) {
                            obj = new OptionsStore({}, path + ".");
                            obj.addObserver(this);
                            this[path] = obj;
                        }

                        if (obj instanceof OptionsStore) {
                            obj.set(parts.join("."), value);
                            return composite;
                        }

                        path += ".";
                    }
                }

                this._clear(field);
                kendo.setter(field)(this, value);

                return composite;
            },

            _clear: function (field) {
                var current = kendo.getter(field, true)(this);
                if (current && current.removeObserver) {
                    current.removeObserver(this);
                }
            },

            _wrap: function (object, field) {
                var type = toString.call(object);

                if (object !== null && defined(object) && type === "[object Object]") {
                    if (!(object instanceof OptionsStore) && !(object instanceof Class)) {
                        object = new OptionsStore(object, this.prefix + field + ".");
                    }

                    object.addObserver(this);
                }

                return object;
            }
        });
        deepExtend(OptionsStore.fn, kendo.mixins.ObserversMixin);

        var SurfaceFactory = function () {
            this._items = [];
        };

        SurfaceFactory.prototype = {
            register: function (name, type, order) {
                var items = this._items,
                first = items[0],
                entry = {
                    name: name,
                    type: type,
                    order: order
                };

                if (!first || order < first.order) {
                    items.unshift(entry);
                } else {
                    items.push(entry);
                }
            },

            create: function (element, options) {
                var items = this._items,
                match = items[0];

                if (options && options.type) {
                    var preferred = options.type.toLowerCase();
                    for (var i = 0; i < items.length; i++) {
                        if (items[i].name === preferred) {
                            match = items[i];
                            break;
                        }
                    }
                }

                if (match) {
                    return new match.type(element, options);
                }

                kendo.logToConsole(
                "Warning: Unable to create Kendo UI Drawing Surface. Possible causes:\n" +
                "- The browser does not support SVG, VML and Canvas. User agent: " + navigator.userAgent + "\n" +
                "- The Kendo UI scripts are not fully loaded");
            }
        };

        SurfaceFactory.current = new SurfaceFactory();

        // Exports ================================================================
        deepExtend(kendo, {
            drawing: {
                DASH_ARRAYS: {
                    dot: [1.5, 3.5],
                    dash: [4, 3.5],
                    longdash: [8, 3.5],
                    dashdot: [3.5, 3.5, 1.5, 3.5],
                    longdashdot: [8, 3.5, 1.5, 3.5],
                    longdashdotdot: [8, 3.5, 1.5, 3.5, 1.5, 3.5]
                },

                Color: kendo.Color,
                BaseNode: BaseNode,
                OptionsStore: OptionsStore,
                Surface: Surface,
                SurfaceFactory: SurfaceFactory
            }
        });

        kendo.dataviz.drawing = kendo.drawing;

    })(window.kendo.jQuery);

    (function ($) {

        // Imports ================================================================
        var kendo = window.kendo,
        deepExtend = kendo.deepExtend,
        defined = kendo.util.defined;

        // Constants ==============================================================
        var GRADIENT = "gradient";

        // Mixins =================================================================
        var Paintable = {
            extend: function (proto) {
                proto.fill = this.fill;
                proto.stroke = this.stroke;
            },

            fill: function (color, opacity) {
                var options = this.options;

                if (defined(color)) {
                    if (color && color.nodeType != GRADIENT) {
                        var newFill = {
                            color: color
                        };
                        if (defined(opacity)) {
                            newFill.opacity = opacity;
                        }
                        options.set("fill", newFill);
                    } else {
                        options.set("fill", color);
                    }

                    return this;
                } else {
                    return options.get("fill");
                }
            },

            stroke: function (color, width, opacity) {
                if (defined(color)) {
                    this.options.set("stroke.color", color);

                    if (defined(width)) {
                        this.options.set("stroke.width", width);
                    }

                    if (defined(opacity)) {
                        this.options.set("stroke.opacity", opacity);
                    }

                    return this;
                } else {
                    return this.options.get("stroke");
                }
            }
        };

        var Traversable = {
            extend: function (proto, childrenField) {
                proto.traverse = function (callback) {
                    var children = this[childrenField];

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

                        if (child.traverse) {
                            child.traverse(callback);
                        } else {
                            callback(child);
                        }
                    }

                    return this;
                };
            }
        };

        // Exports ================================================================
        deepExtend(kendo.drawing, {
            mixins: {
                Paintable: Paintable,
                Traversable: Traversable
            }
        });

    })(window.kendo.jQuery);

    (function ($) {

        // Imports =================================================================
        var doc = document,

        kendo = window.kendo,
        Class = kendo.Class,
        deepExtend = kendo.deepExtend,

        util = kendo.util,
        defined = util.defined;

        // Constants ===============================================================
        var BASELINE_MARKER_SIZE = 1;

        // Text metrics calculations ===============================================
        var LRUCache = Class.extend({
            init: function (size) {
                this._size = size;
                this._length = 0;
                this._map = {};
            },

            put: function (key, value) {
                var lru = this,
                map = lru._map,
                entry = { key: key, value: value };

                map[key] = entry;

                if (!lru._head) {
                    lru._head = lru._tail = entry;
                } else {
                    lru._tail.newer = entry;
                    entry.older = lru._tail;
                    lru._tail = entry;
                }

                if (lru._length >= lru._size) {
                    map[lru._head.key] = null;
                    lru._head = lru._head.newer;
                    lru._head.older = null;
                } else {
                    lru._length++;
                }
            },

            get: function (key) {
                var lru = this,
                entry = lru._map[key];

                if (entry) {
                    if (entry === lru._head && entry !== lru._tail) {
                        lru._head = entry.newer;
                        lru._head.older = null;
                    }

                    if (entry !== lru._tail) {
                        if (entry.older) {
                            entry.older.newer = entry.newer;
                            entry.newer.older = entry.older;
                        }

                        entry.older = lru._tail;
                        entry.newer = null;

                        lru._tail.newer = entry;
                        lru._tail = entry;
                    }

                    return entry.value;
                }
            }
        });

        var TextMetrics = Class.extend({
            init: function () {
                this._cache = new LRUCache(1000);
            },

            measure: function (text, style) {
                var styleKey = util.objectKey(style),
                cacheKey = util.hashKey(text + styleKey),
                cachedResult = this._cache.get(cacheKey);

                if (cachedResult) {
                    return cachedResult;
                }

                var size = { width: 0, height: 0, baseline: 0 };

                var measureBox = this._measureBox,
                baselineMarker = this._baselineMarker.cloneNode(false);

                for (var key in style) {
                    var value = style[key];
                    if (defined(value)) {
                        measureBox.style[key] = value;
                    }
                }

                measureBox.innerHTML = text;
                measureBox.appendChild(baselineMarker);
                doc.body.appendChild(measureBox);

                if ((text + "").length) {
                    size.width = measureBox.offsetWidth - BASELINE_MARKER_SIZE;
                    size.height = measureBox.offsetHeight;
                    size.baseline = baselineMarker.offsetTop + BASELINE_MARKER_SIZE;
                }

                this._cache.put(cacheKey, size);

                measureBox.parentNode.removeChild(measureBox);

                return size;
            }
        });

        TextMetrics.fn._baselineMarker =
        $("<div class='k-baseline-marker' " +
          "style='display: inline-block; vertical-align: baseline;" +
          "width: " + BASELINE_MARKER_SIZE + "px; height: " + BASELINE_MARKER_SIZE + "px;" +
          "overflow: hidden;' />")[0];

        TextMetrics.fn._measureBox =
        $("<div style='position: absolute !important; top: -4000px !important; width: auto !important; height: auto !important;" +
                      "padding: 0 !important; margin: 0 !important; border: 0 !important;" +
                      "line-height: normal !important; visibility: hidden !important; white-space:nowrap !important;' />")[0];

        TextMetrics.current = new TextMetrics();

        function measureText(text, style) {
            return TextMetrics.current.measure(text, style);
        }

        // Exports ================================================================
        deepExtend(kendo.drawing, {
            util: {
                TextMetrics: TextMetrics,
                LRUCache: LRUCache,

                measureText: measureText
            }
        });

    })(window.kendo.jQuery);

    (function ($) {

        // Imports ================================================================
        var kendo = window.kendo,
        Class = kendo.Class,
        deepExtend = kendo.deepExtend,

        g = kendo.geometry,
        Point = g.Point,
        Rect = g.Rect,
        Size = g.Size,
        Matrix = g.Matrix,
        toMatrix = g.toMatrix,

        drawing = kendo.drawing,
        OptionsStore = drawing.OptionsStore,

        math = Math,
        pow = math.pow,

        util = kendo.util,
        append = util.append,
        arrayLimits = util.arrayLimits,
        defined = util.defined,
        last = util.last,
        valueOrDefault = util.valueOrDefault,
        ObserversMixin = kendo.mixins.ObserversMixin,

        inArray = $.inArray,
        push = [].push,
        pop = [].pop,
        splice = [].splice,
        shift = [].shift,
        slice = [].slice,
        unshift = [].unshift;

        // Drawing primitives =====================================================
        var Element = Class.extend({
            nodeType: "Element",

            init: function (options) {
                this._initOptions(options);
            },

            _initOptions: function (options) {
                options = options || {};

                var transform = options.transform;
                var clip = options.clip;

                if (transform) {
                    options.transform = g.transform(transform);
                }

                if (clip && !clip.id) {
                    clip.id = kendo.guid();
                }

                this.options = new OptionsStore(options);
                this.options.addObserver(this);
            },

            transform: function (transform) {
                if (defined(transform)) {
                    this.options.set("transform", g.transform(transform));
                } else {
                    return this.options.get("transform");
                }
            },

            parentTransform: function () {
                var element = this,
                transformation,
                matrix,
                parentMatrix;

                while (element.parent) {
                    element = element.parent;
                    transformation = element.transform();
                    if (transformation) {
                        parentMatrix = transformation.matrix().multiplyCopy(parentMatrix || Matrix.unit());
                    }
                }

                if (parentMatrix) {
                    return g.transform(parentMatrix);
                }
            },

            currentTransform: function (parentTransform) {
                var elementTransform = this.transform(),
                elementMatrix = toMatrix(elementTransform),
                parentMatrix,
                combinedMatrix;

                if (!defined(parentTransform)) {
                    parentTransform = this.parentTransform();
                }

                parentMatrix = toMatrix(parentTransform);

                if (elementMatrix && parentMatrix) {
                    combinedMatrix = parentMatrix.multiplyCopy(elementMatrix);
                } else {
                    combinedMatrix = elementMatrix || parentMatrix;
                }

                if (combinedMatrix) {
                    return g.transform(combinedMatrix);
                }
            },

            visible: function (visible) {
                if (defined(visible)) {
                    this.options.set("visible", visible);
                    return this;
                } else {
                    return this.options.get("visible") !== false;
                }
            },

            clip: function (clip) {
                var options = this.options;
                if (defined(clip)) {
                    if (clip && !clip.id) {
                        clip.id = kendo.guid();
                    }
                    options.set("clip", clip);
                    return this;
                } else {
                    return options.get("clip");
                }
            },

            opacity: function (value) {
                if (defined(value)) {
                    this.options.set("opacity", value);
                    return this;
                } else {
                    return valueOrDefault(this.options.get("opacity"), 1);
                }
            },

            clippedBBox: function (transformation) {
                var box = this._clippedBBox(transformation);
                if (box) {
                    var clip = this.clip();
                    return clip ? Rect.intersect(box, clip.bbox(transformation)) : box;
                }
            },

            _clippedBBox: function (transformation) {
                return this.bbox(transformation);
            }
        });

        deepExtend(Element.fn, ObserversMixin);

        var ElementsArray = Class.extend({
            init: function (array) {
                array = array || [];

                this.length = 0;
                this._splice(0, array.length, array);
            },

            elements: function (elements) {
                if (elements) {
                    this._splice(0, this.length, elements);

                    this._change();
                    return this;
                } else {
                    return this.slice(0);
                }
            },

            push: function () {
                var elements = arguments;
                var result = push.apply(this, elements);

                this._add(elements);

                return result;
            },

            slice: slice,

            pop: function () {
                var length = this.length;
                var result = pop.apply(this);

                if (length) {
                    this._remove([result]);
                }

                return result;
            },

            splice: function (index, howMany) {
                var elements = slice.call(arguments, 2);
                var result = this._splice(index, howMany, elements);

                this._change();

                return result;
            },

            shift: function () {
                var length = this.length;
                var result = shift.apply(this);

                if (length) {
                    this._remove([result]);
                }

                return result;
            },

            unshift: function () {
                var elements = arguments;
                var result = unshift.apply(this, elements);

                this._add(elements);

                return result;
            },

            indexOf: function (element) {
                var that = this;
                var idx;
                var length;

                for (idx = 0, length = that.length; idx < length; idx++) {
                    if (that[idx] === element) {
                        return idx;
                    }
                }
                return -1;
            },

            _splice: function (index, howMany, elements) {
                var result = splice.apply(this, [index, howMany].concat(elements));

                this._clearObserver(result);
                this._setObserver(elements);

                return result;
            },

            _add: function (elements) {
                this._setObserver(elements);
                this._change();
            },

            _remove: function (elements) {
                this._clearObserver(elements);
                this._change();
            },

            _setObserver: function (elements) {
                for (var idx = 0; idx < elements.length; idx++) {
                    elements[idx].addObserver(this);
                }
            },

            _clearObserver: function (elements) {
                for (var idx = 0; idx < elements.length; idx++) {
                    elements[idx].removeObserver(this);
                }
            },

            _change: function () { }
        });

        deepExtend(ElementsArray.fn, ObserversMixin);

        var Group = Element.extend({
            nodeType: "Group",

            init: function (options) {
                Element.fn.init.call(this, options);
                this.children = [];
            },

            childrenChange: function (action, items, index) {
                this.trigger("childrenChange", {
                    action: action,
                    items: items,
                    index: index
                });
            },

            append: function () {
                append(this.children, arguments);
                this._reparent(arguments, this);

                this.childrenChange("add", arguments);

                return this;
            },

            insertAt: function (element, index) {
                this.children.splice(index, 0, element);
                element.parent = this;

                this.childrenChange("add", [element], index);

                return this;
            },

            remove: function (element) {
                var index = inArray(element, this.children);
                if (index >= 0) {
                    this.children.splice(index, 1);
                    element.parent = null;
                    this.childrenChange("remove", [element], index);
                }

                return this;
            },

            removeAt: function (index) {
                if (0 <= index && index < this.children.length) {
                    var element = this.children[index];
                    this.children.splice(index, 1);
                    element.parent = null;
                    this.childrenChange("remove", [element], index);
                }

                return this;
            },

            clear: function () {
                var items = this.children;
                this.children = [];
                this._reparent(items, null);

                this.childrenChange("remove", items, 0);

                return this;
            },

            bbox: function (transformation) {
                return elementsBoundingBox(this.children, true, this.currentTransform(transformation));
            },

            rawBBox: function () {
                return elementsBoundingBox(this.children, false);
            },

            _clippedBBox: function (transformation) {
                return elementsClippedBoundingBox(this.children, this.currentTransform(transformation));
            },

            currentTransform: function (transformation) {
                return Element.fn.currentTransform.call(this, transformation) || null;
            },

            _reparent: function (elements, newParent) {
                for (var i = 0; i < elements.length; i++) {
                    var child = elements[i];
                    var parent = child.parent;
                    if (parent && parent != this && parent.remove) {
                        parent.remove(child);
                    }

                    child.parent = newParent;
                }
            }
        });
        drawing.mixins.Traversable.extend(Group.fn, "children");

        var Text = Element.extend({
            nodeType: "Text",

            init: function (content, position, options) {
                Element.fn.init.call(this, options);

                this.content(content);
                this.position(position || new g.Point());

                if (!this.options.font) {
                    this.options.font = "12px sans-serif";
                }

                if (!defined(this.options.fill)) {
                    this.fill("#000");
                }
            },

            content: function (value) {
                if (defined(value)) {
                    this.options.set("content", value);
                    return this;
                } else {
                    return this.options.get("content");
                }
            },

            measure: function () {
                var metrics = drawing.util.measureText(this.content(), {
                    font: this.options.get("font")
                });

                return metrics;
            },

            rect: function () {
                var size = this.measure();
                var pos = this.position().clone();
                return new g.Rect(pos, [size.width, size.height]);
            },

            bbox: function (transformation) {
                var combinedMatrix = toMatrix(this.currentTransform(transformation));
                return this.rect().bbox(combinedMatrix);
            },

            rawBBox: function () {
                return this.rect().bbox();
            }
        });
        drawing.mixins.Paintable.extend(Text.fn);
        definePointAccessors(Text.fn, ["position"]);

        var Circle = Element.extend({
            nodeType: "Circle",

            init: function (geometry, options) {
                Element.fn.init.call(this, options);
                this.geometry(geometry || new g.Circle());

                if (!defined(this.options.stroke)) {
                    this.stroke("#000");
                }
            },

            bbox: function (transformation) {
                var combinedMatrix = toMatrix(this.currentTransform(transformation));
                var rect = this._geometry.bbox(combinedMatrix);
                var strokeWidth = this.options.get("stroke.width");
                if (strokeWidth) {
                    expandRect(rect, strokeWidth / 2);
                }

                return rect;
            },

            rawBBox: function () {
                return this._geometry.bbox();
            }
        });
        drawing.mixins.Paintable.extend(Circle.fn);
        defineGeometryAccessors(Circle.fn, ["geometry"]);

        var Arc = Element.extend({
            nodeType: "Arc",

            init: function (geometry, options) {
                Element.fn.init.call(this, options);
                this.geometry(geometry || new g.Arc());

                if (!defined(this.options.stroke)) {
                    this.stroke("#000");
                }
            },

            bbox: function (transformation) {
                var combinedMatrix = toMatrix(this.currentTransform(transformation));
                var rect = this.geometry().bbox(combinedMatrix);
                var strokeWidth = this.options.get("stroke.width");

                if (strokeWidth) {
                    expandRect(rect, strokeWidth / 2);
                }

                return rect;
            },

            rawBBox: function () {
                return this.geometry().bbox();
            },

            toPath: function () {
                var path = new Path();
                var curvePoints = this.geometry().curvePoints();

                if (curvePoints.length > 0) {
                    path.moveTo(curvePoints[0].x, curvePoints[0].y);

                    for (var i = 1; i < curvePoints.length; i += 3) {
                        path.curveTo(curvePoints[i], curvePoints[i + 1], curvePoints[i + 2]);
                    }
                }

                return path;
            }
        });
        drawing.mixins.Paintable.extend(Arc.fn);
        defineGeometryAccessors(Arc.fn, ["geometry"]);

        var GeometryElementsArray = ElementsArray.extend({
            _change: function () {
                this.geometryChange();
            }
        });

        var Segment = Class.extend({
            init: function (anchor, controlIn, controlOut) {
                this.anchor(anchor || new Point());
                this.controlIn(controlIn);
                this.controlOut(controlOut);
            },

            bboxTo: function (toSegment, matrix) {
                var rect;
                var segmentAnchor = this.anchor().transformCopy(matrix);
                var toSegmentAnchor = toSegment.anchor().transformCopy(matrix);

                if (this.controlOut() && toSegment.controlIn()) {
                    rect = this._curveBoundingBox(
                    segmentAnchor, this.controlOut().transformCopy(matrix),
                    toSegment.controlIn().transformCopy(matrix), toSegmentAnchor
                );
                } else {
                    rect = this._lineBoundingBox(segmentAnchor, toSegmentAnchor);
                }

                return rect;
            },

            _lineBoundingBox: function (p1, p2) {
                return Rect.fromPoints(p1, p2);
            },

            _curveBoundingBox: function (p1, cp1, cp2, p2) {
                var points = [p1, cp1, cp2, p2],
                extremesX = this._curveExtremesFor(points, "x"),
                extremesY = this._curveExtremesFor(points, "y"),
                xLimits = arrayLimits([extremesX.min, extremesX.max, p1.x, p2.x]),
                yLimits = arrayLimits([extremesY.min, extremesY.max, p1.y, p2.y]);

                return Rect.fromPoints(new Point(xLimits.min, yLimits.min), new Point(xLimits.max, yLimits.max));
            },

            _curveExtremesFor: function (points, field) {
                var extremes = this._curveExtremes(
                points[0][field], points[1][field],
                points[2][field], points[3][field]
            );

                return {
                    min: this._calculateCurveAt(extremes.min, field, points),
                    max: this._calculateCurveAt(extremes.max, field, points)
                };
            },

            _calculateCurveAt: function (t, field, points) {
                var t1 = 1 - t;

                return pow(t1, 3) * points[0][field] +
                   3 * pow(t1, 2) * t * points[1][field] +
                   3 * pow(t, 2) * t1 * points[2][field] +
                   pow(t, 3) * points[3][field];
            },

            _curveExtremes: function (x1, x2, x3, x4) {
                var a = x1 - 3 * x2 + 3 * x3 - x4;
                var b = -2 * (x1 - 2 * x2 + x3);
                var c = x1 - x2;
                var sqrt = math.sqrt(b * b - 4 * a * c);
                var t1 = 0;
                var t2 = 1;

                if (a === 0) {
                    if (b !== 0) {
                        t1 = t2 = -c / b;
                    }
                } else if (!isNaN(sqrt)) {
                    t1 = (-b + sqrt) / (2 * a);
                    t2 = (-b - sqrt) / (2 * a);
                }

                var min = math.max(math.min(t1, t2), 0);
                if (min < 0 || min > 1) {
                    min = 0;
                }

                var max = math.min(math.max(t1, t2), 1);
                if (max > 1 || max < 0) {
                    max = 1;
                }

                return {
                    min: min,
                    max: max
                };
            }
        });
        definePointAccessors(Segment.fn, ["anchor", "controlIn", "controlOut"]);
        deepExtend(Segment.fn, ObserversMixin);

        var Path = Element.extend({
            nodeType: "Path",

            init: function (options) {
                Element.fn.init.call(this, options);
                this.segments = new GeometryElementsArray();
                this.segments.addObserver(this);

                if (!defined(this.options.stroke)) {
                    this.stroke("#000");

                    if (!defined(this.options.stroke.lineJoin)) {
                        this.options.set("stroke.lineJoin", "miter");
                    }
                }
            },

            moveTo: function (x, y) {
                this.suspend();
                this.segments.elements([]);
                this.resume();

                this.lineTo(x, y);

                return this;
            },

            lineTo: function (x, y) {
                var point = defined(y) ? new Point(x, y) : x,
                segment = new Segment(point);

                this.segments.push(segment);

                return this;
            },

            curveTo: function (controlOut, controlIn, point) {
                if (this.segments.length > 0) {
                    var lastSegment = last(this.segments);
                    var segment = new Segment(point, controlIn);
                    this.suspend();
                    lastSegment.controlOut(controlOut);
                    this.resume();

                    this.segments.push(segment);
                }

                return this;
            },

            arc: function (startAngle, endAngle, radiusX, radiusY, anticlockwise) {
                if (this.segments.length > 0) {
                    var lastSegment = last(this.segments);
                    var anchor = lastSegment.anchor();
                    var start = util.rad(startAngle);
                    var center = new Point(anchor.x - radiusX * math.cos(start),
                    anchor.y - radiusY * math.sin(start));
                    var arc = new g.Arc(center, {
                        startAngle: startAngle,
                        endAngle: endAngle,
                        radiusX: radiusX,
                        radiusY: radiusY,
                        anticlockwise: anticlockwise
                    });

                    this._addArcSegments(arc);
                }

                return this;
            },

            arcTo: function (end, rx, ry, largeArc, swipe) {
                if (this.segments.length > 0) {
                    var lastSegment = last(this.segments);
                    var anchor = lastSegment.anchor();
                    var arc = g.Arc.fromPoints(anchor, end, rx, ry, largeArc, swipe);

                    this._addArcSegments(arc);
                }
                return this;
            },

            _addArcSegments: function (arc) {
                this.suspend();
                var curvePoints = arc.curvePoints();
                for (var i = 1; i < curvePoints.length; i += 3) {
                    this.curveTo(curvePoints[i], curvePoints[i + 1], curvePoints[i + 2]);
                }
                this.resume();
                this.geometryChange();
            },

            close: function () {
                this.options.closed = true;
                this.geometryChange();

                return this;
            },

            bbox: function (transformation) {
                var combinedMatrix = toMatrix(this.currentTransform(transformation));
                var boundingBox = this._bbox(combinedMatrix);
                var strokeWidth = this.options.get("stroke.width");
                if (strokeWidth) {
                    expandRect(boundingBox, strokeWidth / 2);
                }
                return boundingBox;
            },

            rawBBox: function () {
                return this._bbox();
            },

            _bbox: function (matrix) {
                var segments = this.segments;
                var length = segments.length;
                var boundingBox;

                if (length === 1) {
                    var anchor = segments[0].anchor().transformCopy(matrix);
                    boundingBox = new Rect(anchor, Size.ZERO);
                } else if (length > 0) {
                    for (var i = 1; i < length; i++) {
                        var segmentBox = segments[i - 1].bboxTo(segments[i], matrix);
                        if (boundingBox) {
                            boundingBox = Rect.union(boundingBox, segmentBox);
                        } else {
                            boundingBox = segmentBox;
                        }
                    }
                }

                return boundingBox;
            }
        });
        drawing.mixins.Paintable.extend(Path.fn);

        Path.fromRect = function (rect, options) {
            return new Path(options)
            .moveTo(rect.topLeft())
            .lineTo(rect.topRight())
            .lineTo(rect.bottomRight())
            .lineTo(rect.bottomLeft())
            .close();
        };

        Path.fromPoints = function (points, options) {
            if (points) {
                var path = new Path(options);

                for (var i = 0; i < points.length; i++) {
                    var pt = Point.create(points[i]);
                    if (pt) {
                        if (i === 0) {
                            path.moveTo(pt);
                        } else {
                            path.lineTo(pt);
                        }
                    }
                }

                return path;
            }
        };

        Path.fromArc = function (arc, options) {
            var path = new Path(options);
            var startAngle = arc.startAngle;
            var start = arc.pointAt(startAngle);
            path.moveTo(start.x, start.y);
            path.arc(startAngle, arc.endAngle, arc.radiusX, arc.radiusY, arc.anticlockwise);
            return path;
        };

        var MultiPath = Element.extend({
            nodeType: "MultiPath",

            init: function (options) {
                Element.fn.init.call(this, options);
                this.paths = new GeometryElementsArray();
                this.paths.addObserver(this);

                if (!defined(this.options.stroke)) {
                    this.stroke("#000");
                }
            },

            moveTo: function (x, y) {
                var path = new Path();
                path.moveTo(x, y);

                this.paths.push(path);

                return this;
            },

            lineTo: function (x, y) {
                if (this.paths.length > 0) {
                    last(this.paths).lineTo(x, y);
                }

                return this;
            },

            curveTo: function (controlOut, controlIn, point) {
                if (this.paths.length > 0) {
                    last(this.paths).curveTo(controlOut, controlIn, point);
                }

                return this;
            },

            arc: function (startAngle, endAngle, radiusX, radiusY, anticlockwise) {
                if (this.paths.length > 0) {
                    last(this.paths).arc(startAngle, endAngle, radiusX, radiusY, anticlockwise);
                }

                return this;
            },

            arcTo: function (end, rx, ry, largeArc, swipe) {
                if (this.paths.length > 0) {
                    last(this.paths).arcTo(end, rx, ry, largeArc, swipe);
                }

                return this;
            },

            close: function () {
                if (this.paths.length > 0) {
                    last(this.paths).close();
                }

                return this;
            },

            bbox: function (transformation) {
                return elementsBoundingBox(this.paths, true, this.currentTransform(transformation));
            },

            rawBBox: function () {
                return elementsBoundingBox(this.paths, false);
            },

            _clippedBBox: function (transformation) {
                return elementsClippedBoundingBox(this.paths, this.currentTransform(transformation));
            }
        });
        drawing.mixins.Paintable.extend(MultiPath.fn);

        var Image = Element.extend({
            nodeType: "Image",

            init: function (src, rect, options) {
                Element.fn.init.call(this, options);

                this.src(src);
                this.rect(rect || new g.Rect());
            },

            src: function (value) {
                if (defined(value)) {
                    this.options.set("src", value);
                    return this;
                } else {
                    return this.options.get("src");
                }
            },

            bbox: function (transformation) {
                var combinedMatrix = toMatrix(this.currentTransform(transformation));
                return this._rect.bbox(combinedMatrix);
            },

            rawBBox: function () {
                return this._rect.bbox();
            }
        });
        defineGeometryAccessors(Image.fn, ["rect"]);

        var GradientStop = Class.extend({
            init: function (offset, color, opacity) {
                this.options = new OptionsStore({
                    offset: offset,
                    color: color,
                    opacity: defined(opacity) ? opacity : 1
                });
                this.options.addObserver(this);
            }
        });

        defineOptionsAccessors(GradientStop.fn, ["offset", "color", "opacity"]);
        deepExtend(GradientStop.fn, ObserversMixin);

        GradientStop.create = function (arg) {
            if (defined(arg)) {
                var stop;
                if (arg instanceof GradientStop) {
                    stop = arg;
                } else if (arg.length > 1) {
                    stop = new GradientStop(arg[0], arg[1], arg[2]);
                } else {
                    stop = new GradientStop(arg.offset, arg.color, arg.opacity);
                }

                return stop;
            }
        };

        var StopsArray = ElementsArray.extend({
            _change: function () {
                this.optionsChange({
                    field: "stops"
                });
            }
        });

        var Gradient = Class.extend({
            nodeType: "gradient",

            init: function (options) {
                this.stops = new StopsArray(this._createStops(options.stops));
                this.stops.addObserver(this);
                this._userSpace = options.userSpace;
                this.id = kendo.guid();
            },

            userSpace: function (value) {
                if (defined(value)) {
                    this._userSpace = value;
                    this.optionsChange();
                    return this;
                } else {
                    return this._userSpace;
                }
            },

            _createStops: function (stops) {
                var result = [];
                var idx;
                stops = stops || [];
                for (idx = 0; idx < stops.length; idx++) {
                    result.push(GradientStop.create(stops[idx]));
                }

                return result;
            },

            addStop: function (offset, color, opacity) {
                this.stops.push(new GradientStop(offset, color, opacity));
            },

            removeStop: function (stop) {
                var index = this.stops.indexOf(stop);
                if (index >= 0) {
                    this.stops.splice(index, 1);
                }
            }
        });

        deepExtend(Gradient.fn, ObserversMixin, {
            optionsChange: function (e) {
                this.trigger("optionsChange", {
                    field: "gradient" + (e ? "." + e.field : ""),
                    value: this
                });
            },

            geometryChange: function () {
                this.optionsChange();
            }
        });

        var LinearGradient = Gradient.extend({
            init: function (options) {
                options = options || {};
                Gradient.fn.init.call(this, options);

                this.start(options.start || new Point());

                this.end(options.end || new Point(1, 0));
            }
        });

        definePointAccessors(LinearGradient.fn, ["start", "end"]);

        var RadialGradient = Gradient.extend({
            init: function (options) {
                options = options || {};
                Gradient.fn.init.call(this, options);

                this.center(options.center || new Point());
                this._radius = defined(options.radius) ? options.radius : 1;
                this._fallbackFill = options.fallbackFill;
            },

            radius: function (value) {
                if (defined(value)) {
                    this._radius = value;
                    this.geometryChange();
                    return this;
                } else {
                    return this._radius;
                }
            },

            fallbackFill: function (value) {
                if (defined(value)) {
                    this._fallbackFill = value;
                    this.optionsChange();
                    return this;
                } else {
                    return this._fallbackFill;
                }
            }
        });

        definePointAccessors(RadialGradient.fn, ["center"]);

        // Helper functions ===========================================
        function elementsBoundingBox(elements, applyTransform, transformation) {
            var boundingBox;

            for (var i = 0; i < elements.length; i++) {
                var element = elements[i];
                if (element.visible()) {
                    var elementBoundingBox = applyTransform ? element.bbox(transformation) : element.rawBBox();
                    if (elementBoundingBox) {
                        if (boundingBox) {
                            boundingBox = Rect.union(boundingBox, elementBoundingBox);
                        } else {
                            boundingBox = elementBoundingBox;
                        }
                    }
                }
            }

            return boundingBox;
        }

        function elementsClippedBoundingBox(elements, applyTransform, transformation) {
            var boundingBox;

            for (var i = 0; i < elements.length; i++) {
                var element = elements[i];
                if (element.visible()) {
                    var elementBoundingBox = applyTransform ? element.clippedBBox(transformation) : element.rawBBox();
                    if (elementBoundingBox) {
                        if (boundingBox) {
                            boundingBox = Rect.union(boundingBox, elementBoundingBox);
                        } else {
                            boundingBox = elementBoundingBox;
                        }
                    }
                }
            }

            return boundingBox;
        }

        function expandRect(rect, value) {
            rect.origin.x -= value;
            rect.origin.y -= value;
            rect.size.width += value * 2;
            rect.size.height += value * 2;
        }

        function defineGeometryAccessors(fn, names) {
            for (var i = 0; i < names.length; i++) {
                fn[names[i]] = geometryAccessor(names[i]);
            }
        }

        function geometryAccessor(name) {
            var fieldName = "_" + name;
            return function (value) {
                if (defined(value)) {
                    this._observerField(fieldName, value);
                    this.geometryChange();
                    return this;
                } else {
                    return this[fieldName];
                }
            };
        }

        function definePointAccessors(fn, names) {
            for (var i = 0; i < names.length; i++) {
                fn[names[i]] = pointAccessor(names[i]);
            }
        }

        function pointAccessor(name) {
            var fieldName = "_" + name;
            return function (value) {
                if (defined(value)) {
                    this._observerField(fieldName, Point.create(value));
                    this.geometryChange();
                    return this;
                } else {
                    return this[fieldName];
                }
            };
        }

        function defineOptionsAccessors(fn, names) {
            for (var i = 0; i < names.length; i++) {
                fn[names[i]] = optionsAccessor(names[i]);
            }
        }

        function optionsAccessor(name) {
            return function (value) {
                if (defined(value)) {
                    this.options.set(name, value);
                    return this;
                } else {
                    return this.options.get(name);
                }
            };
        }

        // Exports ================================================================
        deepExtend(drawing, {
            Arc: Arc,
            Circle: Circle,
            Element: Element,
            ElementsArray: ElementsArray,
            Gradient: Gradient,
            GradientStop: GradientStop,
            Group: Group,
            Image: Image,
            LinearGradient: LinearGradient,
            MultiPath: MultiPath,
            Path: Path,
            RadialGradient: RadialGradient,
            Segment: Segment,
            Text: Text
        });

    })(window.kendo.jQuery);

    (function ($) {

        var kendo = window.kendo,
        drawing = kendo.drawing,
        geometry = kendo.geometry,

        Class = kendo.Class,
        Point = geometry.Point,
        deepExtend = kendo.deepExtend,
        trim = $.trim,
        util = kendo.util,
        deg = util.deg,
        last = util.last,
        round = util.round;

        var SEGMENT_REGEX = /([a-z]{1})([^a-z]*)(z)?/gi,
        SPLIT_REGEX = /[,\s]?(-?(?:\d+\.)?\d+)/g,
        MOVE = "m",
        CLOSE = "z";

        var PathParser = Class.extend({
            parse: function (str, options) {
                var parser = this;
                var multiPath = new drawing.MultiPath(options);
                var position = new Point();
                var previousCommand;

                str.replace(SEGMENT_REGEX, function (match, element, params, closePath) {
                    var command = element.toLowerCase();
                    var isRelative = command === element;
                    var parameters = parseParameters(trim(params));

                    if (command === MOVE) {
                        if (isRelative) {
                            position.x += parameters[0];
                            position.y += parameters[1];
                        } else {
                            position.x = parameters[0];
                            position.y = parameters[1];
                        }

                        multiPath.moveTo(position.x, position.y);

                        if (parameters.length > 2) {
                            command = "l";
                            parameters.splice(0, 2);
                        }
                    }

                    if (ShapeMap[command]) {
                        ShapeMap[command](
                        multiPath, {
                            parameters: parameters,
                            position: position,
                            isRelative: isRelative,
                            previousCommand: previousCommand
                        }
                    );

                        if (closePath && closePath.toLowerCase() === CLOSE) {
                            multiPath.close();
                        }
                    } else if (command !== MOVE) {
                        throw new Error("Error while parsing SVG path. Unsupported command: " + command);
                    }

                    previousCommand = command;
                });

                return multiPath;
            }
        });

        var ShapeMap = {
            l: function (path, options) {
                var parameters = options.parameters;
                var position = options.position;
                for (var i = 0; i < parameters.length; i += 2) {
                    var point = new Point(parameters[i], parameters[i + 1]);

                    if (options.isRelative) {
                        point.translateWith(position);
                    }

                    path.lineTo(point.x, point.y);

                    position.x = point.x;
                    position.y = point.y;
                }
            },

            c: function (path, options) {
                var parameters = options.parameters;
                var position = options.position;
                var controlOut, controlIn, point;

                for (var i = 0; i < parameters.length; i += 6) {
                    controlOut = new Point(parameters[i], parameters[i + 1]);
                    controlIn = new Point(parameters[i + 2], parameters[i + 3]);
                    point = new Point(parameters[i + 4], parameters[i + 5]);
                    if (options.isRelative) {
                        controlIn.translateWith(position);
                        controlOut.translateWith(position);
                        point.translateWith(position);
                    }

                    path.curveTo(controlOut, controlIn, point);

                    position.x = point.x;
                    position.y = point.y;
                }
            },

            v: function (path, options) {
                var value = options.isRelative ? 0 : options.position.x;

                toLineParamaters(options.parameters, true, value);
                this.l(path, options);
            },

            h: function (path, options) {
                var value = options.isRelative ? 0 : options.position.y;

                toLineParamaters(options.parameters, false, value);
                this.l(path, options);
            },

            a: function (path, options) {
                var parameters = options.parameters;
                var position = options.position;
                for (var i = 0; i < parameters.length; i += 7) {
                    var radiusX = parameters[i];
                    var radiusY = parameters[i + 1];
                    var largeArc = parameters[i + 3];
                    var swipe = parameters[i + 4];
                    var endPoint = new Point(parameters[i + 5], parameters[i + 6]);

                    if (options.isRelative) {
                        endPoint.translateWith(position);
                    }

                    path.arcTo(endPoint, radiusX, radiusY, largeArc, swipe);

                    position.x = endPoint.x;
                    position.y = endPoint.y;
                }
            },

            s: function (path, options) {
                var parameters = options.parameters;
                var position = options.position;
                var previousCommand = options.previousCommand;
                var controlOut, endPoint, controlIn, lastControlIn;

                if (previousCommand == "s" || previousCommand == "c") {
                    lastControlIn = last(last(path.paths).segments).controlIn();
                }

                for (var i = 0; i < parameters.length; i += 4) {
                    controlIn = new Point(parameters[i], parameters[i + 1]);
                    endPoint = new Point(parameters[i + 2], parameters[i + 3]);
                    if (options.isRelative) {
                        controlIn.translateWith(position);
                        endPoint.translateWith(position);
                    }

                    if (lastControlIn) {
                        controlOut = reflectionPoint(lastControlIn, position);
                    } else {
                        controlOut = position.clone();
                    }
                    lastControlIn = controlIn;

                    path.curveTo(controlOut, controlIn, endPoint);

                    position.x = endPoint.x;
                    position.y = endPoint.y;
                }
            },

            q: function (path, options) {
                var parameters = options.parameters;
                var position = options.position;
                var cubicControlPoints, endPoint, controlPoint;
                for (var i = 0; i < parameters.length; i += 4) {
                    controlPoint = new Point(parameters[i], parameters[i + 1]);
                    endPoint = new Point(parameters[i + 2], parameters[i + 3]);
                    if (options.isRelative) {
                        controlPoint.translateWith(position);
                        endPoint.translateWith(position);
                    }
                    cubicControlPoints = quadraticToCubicControlPoints(position, controlPoint, endPoint);

                    path.curveTo(cubicControlPoints.controlOut, cubicControlPoints.controlIn, endPoint);

                    position.x = endPoint.x;
                    position.y = endPoint.y;
                }
            },

            t: function (path, options) {
                var parameters = options.parameters;
                var position = options.position;
                var previousCommand = options.previousCommand;
                var cubicControlPoints, controlPoint, endPoint;

                if (previousCommand == "q" || previousCommand == "t") {
                    var lastSegment = last(last(path.paths).segments);
                    controlPoint = lastSegment.controlIn().clone()
                    .translateWith(position.scaleCopy(-1 / 3))
                    .scale(3 / 2);
                }

                for (var i = 0; i < parameters.length; i += 2) {
                    endPoint = new Point(parameters[i], parameters[i + 1]);
                    if (options.isRelative) {
                        endPoint.translateWith(position);
                    }

                    if (controlPoint) {
                        controlPoint = reflectionPoint(controlPoint, position);
                    } else {
                        controlPoint = position.clone();
                    }

                    cubicControlPoints = quadraticToCubicControlPoints(position, controlPoint, endPoint);

                    path.curveTo(cubicControlPoints.controlOut, cubicControlPoints.controlIn, endPoint);

                    position.x = endPoint.x;
                    position.y = endPoint.y;
                }
            }
        };

        // Helper functions =======================================================

        function parseParameters(str) {
            var parameters = [];
            str.replace(SPLIT_REGEX, function (match, number) {
                parameters.push(parseFloat(number));
            });
            return parameters;
        }

        function toLineParamaters(parameters, isVertical, value) {
            var insertPosition = isVertical ? 0 : 1;

            for (var i = 0; i < parameters.length; i += 2) {
                parameters.splice(i + insertPosition, 0, value);
            }
        }

        function reflectionPoint(point, center) {
            if (point && center) {
                return center.scaleCopy(2).translate(-point.x, -point.y);
            }
        }

        function quadraticToCubicControlPoints(position, controlPoint, endPoint) {
            var third = 1 / 3;
            controlPoint = controlPoint.clone().scale(2 / 3);
            return {
                controlOut: controlPoint.clone().translateWith(position.scaleCopy(third)),
                controlIn: controlPoint.translateWith(endPoint.scaleCopy(third))
            };
        }

        // Exports ================================================================
        PathParser.current = new PathParser();

        drawing.Path.parse = function (str, options) {
            return PathParser.current.parse(str, options);
        };

        deepExtend(drawing, {
            PathParser: PathParser
        });

    })(window.kendo.jQuery);

    (function ($) {

        // Imports ================================================================
        var doc = document,

        kendo = window.kendo,
        deepExtend = kendo.deepExtend,

        g = kendo.geometry,
        d = kendo.drawing,
        BaseNode = d.BaseNode,

        util = kendo.util,
        defined = util.defined,
        isTransparent = util.isTransparent,
        renderAttr = util.renderAttr,
        renderAllAttr = util.renderAllAttr,
        renderSize = util.renderSize,
        renderTemplate = util.renderTemplate,
        inArray = $.inArray;

        // Constants ==============================================================
        var BUTT = "butt",
        DASH_ARRAYS = d.DASH_ARRAYS,
        GRADIENT = "gradient",
        NONE = "none",
        NS = ".kendo",
        SOLID = "solid",
        SPACE = " ",
        SQUARE = "square",
        SVG_NS = "http://www.w3.org/2000/svg",
        TRANSFORM = "transform",
        UNDEFINED = "undefined";

        // SVG rendering surface ==================================================
        var Surface = d.Surface.extend({
            init: function (element, options) {
                d.Surface.fn.init.call(this, element, options);

                this._root = new RootNode(this.options);

                renderSVG(this.element[0], this._template(this));
                this._rootElement = this.element[0].firstElementChild;
                alignToScreen(this._rootElement);

                this._root.attachTo(this._rootElement);

                this.element.on("click" + NS, this._click);
                this.element.on("mouseover" + NS, this._mouseenter);
                this.element.on("mouseout" + NS, this._mouseleave);

                this.resize();
            },

            type: "svg",

            destroy: function () {
                if (this._root) {
                    this._root.destroy();
                    this._root = null;
                    this._rootElement = null;
                    this.element.off(NS);
                }
                d.Surface.fn.destroy.call(this);
            },

            translate: function (offset) {
                var viewBox = kendo.format(
                "{0} {1} {2} {3}",
                Math.round(offset.x), Math.round(offset.y),
                this._size.width, this._size.height);

                this._offset = offset;
                this._rootElement.setAttribute("viewBox", viewBox);
            },

            draw: function (element) {
                this._root.load([element]);
            },

            clear: function () {
                this._root.clear();
            },

            svg: function () {
                return "<?xml version='1.0' ?>" + this._template(this);
            },

            _resize: function () {
                if (this._offset) {
                    this.translate(this._offset);
                }
            },

            _template: renderTemplate(
            "<svg style='width: 100%; height: 100%; overflow: hidden;' " +
            "xmlns='" + SVG_NS + "' " + "xmlns:xlink='http://www.w3.org/1999/xlink' " +
            "version='1.1'>#= d._root.render() #</svg>"
        )
        });

        // SVG Node ================================================================
        var Node = BaseNode.extend({
            init: function (srcElement) {
                BaseNode.fn.init.call(this, srcElement);
                this.definitions = {};
            },

            destroy: function () {
                if (this.element) {
                    this.element._kendoNode = null;
                    this.element = null;
                }

                this.clearDefinitions();
                BaseNode.fn.destroy.call(this);
            },

            load: function (elements, pos) {
                var node = this,
                element = node.element,
                childNode,
                srcElement,
                children,
                i;

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

                    childNode = new nodeMap[srcElement.nodeType](srcElement);

                    if (defined(pos)) {
                        node.insertAt(childNode, pos);
                    } else {
                        node.append(childNode);
                    }

                    childNode.createDefinitions();

                    if (children && children.length > 0) {
                        childNode.load(children);
                    }

                    if (element) {
                        childNode.attachTo(element, pos);
                    }
                }
            },

            root: function () {
                var root = this;

                while (root.parent) {
                    root = root.parent;
                }

                return root;
            },

            attachTo: function (domElement, pos) {
                var container = doc.createElement("div");
                renderSVG(container,
                "<svg xmlns='" + SVG_NS + "' version='1.1'>" +
                this.render() +
                "</svg>"
            );

                var element = container.firstChild.firstChild;
                if (element) {
                    if (defined(pos)) {
                        domElement.insertBefore(element, domElement.childNodes[pos]);
                    } else {
                        domElement.appendChild(element);
                    }
                    this.setElement(element);
                }
            },

            setElement: function (element) {
                var nodes = this.childNodes,
                childElement,
                i;

                if (this.element) {
                    this.element._kendoNode = null;
                }

                this.element = element;
                this.element._kendoNode = this;

                for (i = 0; i < nodes.length; i++) {
                    childElement = element.childNodes[i];
                    nodes[i].setElement(childElement);
                }
            },

            clear: function () {
                this.clearDefinitions();

                if (this.element) {
                    this.element.innerHTML = "";
                }

                var children = this.childNodes;
                for (var i = 0; i < children.length; i++) {
                    children[i].destroy();
                }

                this.childNodes = [];
            },

            removeSelf: function () {
                if (this.element) {
                    this.element.parentNode.removeChild(this.element);
                    this.element = null;
                }

                BaseNode.fn.removeSelf.call(this);
            },

            template: renderTemplate(
            "#= d.renderChildren() #"
        ),

            render: function () {
                return this.template(this);
            },

            renderChildren: function () {
                var nodes = this.childNodes,
                output = "",
                i;

                for (i = 0; i < nodes.length; i++) {
                    output += nodes[i].render();
                }

                return output;
            },

            optionsChange: function (e) {
                var field = e.field;
                var value = e.value;

                if (field === "visible") {
                    this.css("display", value ? "" : NONE);
                } else if (DefinitionMap[field] && isDefinition(field, value)) {
                    this.updateDefinition(field, value);
                } else if (field === "opacity") {
                    this.attr("opacity", value);
                }

                BaseNode.fn.optionsChange.call(this, e);
            },

            attr: function (name, value) {
                if (this.element) {
                    this.element.setAttribute(name, value);
                }
            },

            allAttr: function (attrs) {
                for (var i = 0; i < attrs.length; i++) {
                    this.attr(attrs[i][0], attrs[i][1]);
                }
            },

            css: function (name, value) {
                if (this.element) {
                    this.element.style[name] = value;
                }
            },

            allCss: function (styles) {
                for (var i = 0; i < styles.length; i++) {
                    this.css(styles[i][0], styles[i][1]);
                }
            },

            removeAttr: function (name) {
                if (this.element) {
                    this.element.removeAttribute(name);
                }
            },

            mapTransform: function (transform) {
                var attrs = [];
                if (transform) {
                    attrs.push([
                   TRANSFORM,
                   "matrix(" + transform.matrix().toString(6) + ")"
                ]);
                }

                return attrs;
            },

            renderTransform: function () {
                return renderAllAttr(
                this.mapTransform(this.srcElement.transform())
            );
            },

            transformChange: function (value) {
                if (value) {
                    this.allAttr(this.mapTransform(value));
                } else {
                    this.removeAttr(TRANSFORM);
                }
            },

            mapStyle: function () {
                var options = this.srcElement.options;
                var style = [["cursor", options.cursor]];

                if (options.visible === false) {
                    style.push(["display", NONE]);
                }

                return style;
            },

            renderStyle: function () {
                return renderAttr("style", util.renderStyle(this.mapStyle()));
            },

            renderOpacity: function () {
                return renderAttr("opacity", this.srcElement.options.opacity);
            },

            createDefinitions: function () {
                var srcElement = this.srcElement;
                var definitions = this.definitions;
                var definition, field, options, hasDefinitions;
                if (srcElement) {
                    options = srcElement.options;

                    for (field in DefinitionMap) {
                        definition = options.get(field);
                        if (definition && isDefinition(field, definition)) {
                            definitions[field] = definition;
                            hasDefinitions = true;
                        }
                    }
                    if (hasDefinitions) {
                        this.definitionChange({
                            action: "add",
                            definitions: definitions
                        });
                    }
                }
            },

            definitionChange: function (e) {
                if (this.parent) {
                    this.parent.definitionChange(e);
                }
            },

            updateDefinition: function (type, value) {
                var definitions = this.definitions;
                var current = definitions[type];
                var attr = DefinitionMap[type];
                var definition = {};
                if (current) {
                    definition[type] = current;
                    this.definitionChange({
                        action: "remove",
                        definitions: definition
                    });
                    delete definitions[type];
                }

                if (!value) {
                    if (current) {
                        this.removeAttr(attr);
                    }
                } else {
                    definition[type] = value;
                    this.definitionChange({
                        action: "add",
                        definitions: definition
                    });
                    definitions[type] = value;
                    this.attr(attr, refUrl(value.id));
                }
            },

            clearDefinitions: function () {
                var definitions = this.definitions;
                var field;

                for (field in definitions) {
                    this.definitionChange({
                        action: "remove",
                        definitions: definitions
                    });
                    this.definitions = {};
                    break;
                }
            },

            renderDefinitions: function () {
                return renderAllAttr(this.mapDefinitions());
            },

            mapDefinitions: function () {
                var definitions = this.definitions;
                var attrs = [];
                var field;
                for (field in definitions) {
                    attrs.push([DefinitionMap[field], refUrl(definitions[field].id)]);
                }

                return attrs;
            }
        });

        var RootNode = Node.extend({
            init: function (options) {
                Node.fn.init.call(this);
                this.options = options;
                this.defs = new DefinitionNode();
            },

            attachTo: function (domElement) {
                this.element = domElement;
                this.defs.attachTo(domElement.firstElementChild);
            },

            clear: function () {
                BaseNode.fn.clear.call(this);
            },

            template: renderTemplate(
            "#=d.defs.render()##= d.renderChildren() #"
        ),

            definitionChange: function (e) {
                this.defs.definitionChange(e);
            }
        });

        var DefinitionNode = Node.extend({
            init: function () {
                Node.fn.init.call(this);
                this.definitionMap = {};
            },

            attachTo: function (domElement) {
                this.element = domElement;
            },

            template: renderTemplate(
            "<defs>#= d.renderChildren()#</defs>"
        ),

            definitionChange: function (e) {
                var definitions = e.definitions;
                var action = e.action;

                if (action == "add") {
                    this.addDefinitions(definitions);
                } else if (action == "remove") {
                    this.removeDefinitions(definitions);
                }
            },

            createDefinition: function (type, item) {
                var nodeType;
                if (type == "clip") {
                    nodeType = ClipNode;
                } else if (type == "fill") {
                    if (item instanceof d.LinearGradient) {
                        nodeType = LinearGradientNode;
                    } else if (item instanceof d.RadialGradient) {
                        nodeType = RadialGradientNode;
                    }
                }
                return new nodeType(item);
            },

            addDefinitions: function (definitions) {
                for (var field in definitions) {
                    this.addDefinition(field, definitions[field]);
                }
            },

            addDefinition: function (type, srcElement) {
                var definitionMap = this.definitionMap;
                var id = srcElement.id;
                var element = this.element;
                var node, mapItem;

                mapItem = definitionMap[id];
                if (!mapItem) {
                    node = this.createDefinition(type, srcElement);
                    definitionMap[id] = {
                        element: node,
                        count: 1
                    };
                    this.append(node);
                    if (element) {
                        node.attachTo(this.element);
                    }
                } else {
                    mapItem.count++;
                }
            },

            removeDefinitions: function (definitions) {
                for (var field in definitions) {
                    this.removeDefinition(definitions[field]);
                }
            },

            removeDefinition: function (srcElement) {
                var definitionMap = this.definitionMap;
                var id = srcElement.id;
                var mapItem;

                mapItem = definitionMap[id];
                if (mapItem) {
                    mapItem.count--;
                    if (mapItem.count === 0) {
                        this.remove(inArray(mapItem.element, this.childNodes), 1);
                        delete definitionMap[id];
                    }
                }
            }
        });

        var ClipNode = Node.extend({
            init: function (srcElement) {
                Node.fn.init.call(this);

                this.srcElement = srcElement;
                this.id = srcElement.id;

                this.load([srcElement]);
            },

            template: renderTemplate(
            "<clipPath id='#=d.id#'>#= d.renderChildren()#</clipPath>"
        )
        });

        var GroupNode = Node.extend({
            template: renderTemplate(
            "<g#= d.renderTransform() + d.renderStyle() + d.renderOpacity() + d.renderDefinitions()#>#= d.renderChildren() #</g>"
        ),

            optionsChange: function (e) {
                if (e.field == TRANSFORM) {
                    this.transformChange(e.value);
                }

                Node.fn.optionsChange.call(this, e);
            }
        });

        var PathNode = Node.extend({
            geometryChange: function () {
                this.attr("d", this.renderData());
                this.invalidate();
            },

            optionsChange: function (e) {
                switch (e.field) {
                    case "fill":
                        if (e.value) {
                            this.allAttr(this.mapFill(e.value));
                        } else {
                            this.removeAttr("fill");
                        }
                        break;

                    case "fill.color":
                        this.allAttr(this.mapFill({ color: e.value }));
                        break;

                    case "stroke":
                        if (e.value) {
                            this.allAttr(this.mapStroke(e.value));
                        } else {
                            this.removeAttr("stroke");
                        }
                        break;

                    case TRANSFORM:
                        this.transformChange(e.value);
                        break;

                    default:
                        var name = this.attributeMap[e.field];
                        if (name) {
                            this.attr(name, e.value);
                        }
                        break;
                }

                Node.fn.optionsChange.call(this, e);
            },

            attributeMap: {
                "fill.opacity": "fill-opacity",
                "stroke.color": "stroke",
                "stroke.width": "stroke-width",
                "stroke.opacity": "stroke-opacity"
            },

            content: function (value) {
                if (this.element) {
                    this.element.textContent = this.srcElement.content();
                }
            },

            renderData: function () {
                return this.printPath(this.srcElement);
            },

            printPath: function (path) {
                var segments = path.segments,
                length = segments.length;
                if (length > 0) {
                    var parts = [],
                    output,
                    segmentType,
                    currentType,
                    i;

                    for (i = 1; i < length; i++) {
                        segmentType = this.segmentType(segments[i - 1], segments[i]);
                        if (segmentType !== currentType) {
                            currentType = segmentType;
                            parts.push(segmentType);
                        }

                        if (segmentType === "L") {
                            parts.push(this.printPoints(segments[i].anchor()));
                        } else {
                            parts.push(this.printPoints(segments[i - 1].controlOut(), segments[i].controlIn(), segments[i].anchor()));
                        }
                    }

                    output = "M" + this.printPoints(segments[0].anchor()) + SPACE + parts.join(SPACE);
                    if (path.options.closed) {
                        output += "Z";
                    }

                    return output;
                }
            },

            printPoints: function () {
                var points = arguments,
                length = points.length,
                i, result = [];

                for (i = 0; i < length; i++) {
                    result.push(points[i].toString(3));
                }

                return result.join(SPACE);
            },

            segmentType: function (segmentStart, segmentEnd) {
                return segmentStart.controlOut() && segmentEnd.controlIn() ? "C" : "L";
            },

            mapStroke: function (stroke) {
                var attrs = [];

                if (stroke && !isTransparent(stroke.color)) {
                    attrs.push(["stroke", stroke.color]);
                    attrs.push(["stroke-width", stroke.width]);
                    attrs.push(["stroke-linecap", this.renderLinecap(stroke)]);
                    attrs.push(["stroke-linejoin", stroke.lineJoin]);

                    if (defined(stroke.opacity)) {
                        attrs.push(["stroke-opacity", stroke.opacity]);
                    }

                    if (defined(stroke.dashType)) {
                        attrs.push(["stroke-dasharray", this.renderDashType(stroke)]);
                    }
                } else {
                    attrs.push(["stroke", NONE]);
                }

                return attrs;
            },

            renderStroke: function () {
                return renderAllAttr(
                this.mapStroke(this.srcElement.options.stroke)
            );
            },

            renderDashType: function (stroke) {
                var width = stroke.width || 1,
                dashType = stroke.dashType;

                if (dashType && dashType != SOLID) {
                    var dashArray = DASH_ARRAYS[dashType.toLowerCase()],
                    result = [],
                    i;

                    for (i = 0; i < dashArray.length; i++) {
                        result.push(dashArray[i] * width);
                    }

                    return result.join(" ");
                }
            },

            renderLinecap: function (stroke) {
                var dashType = stroke.dashType,
                lineCap = stroke.lineCap;

                return (dashType && dashType != SOLID) ? BUTT : lineCap;
            },

            mapFill: function (fill) {
                var attrs = [];
                if (!(fill && fill.nodeType == GRADIENT)) {
                    if (fill && !isTransparent(fill.color)) {
                        attrs.push(["fill", fill.color]);

                        if (defined(fill.opacity)) {
                            attrs.push(["fill-opacity", fill.opacity]);
                        }
                    } else {
                        attrs.push(["fill", NONE]);
                    }
                }

                return attrs;
            },

            renderFill: function () {
                return renderAllAttr(
                this.mapFill(this.srcElement.options.fill)
            );
            },

            template: renderTemplate(
            "<path #= d.renderStyle() # #= d.renderOpacity() # " +
            "#= kendo.util.renderAttr('d', d.renderData()) # " +
            "#= d.renderStroke() # " +
            "#= d.renderFill() # " +
            "#= d.renderDefinitions() # " +
            "#= d.renderTransform() #></path>"
        )
        });

        var ArcNode = PathNode.extend({
            renderData: function () {
                return this.printPath(this.srcElement.toPath());
            }
        });

        var MultiPathNode = PathNode.extend({
            renderData: function () {
                var paths = this.srcElement.paths;

                if (paths.length > 0) {
                    var result = [],
                    i;

                    for (i = 0; i < paths.length; i++) {
                        result.push(this.printPath(paths[i]));
                    }

                    return result.join(" ");
                }
            }
        });

        var CircleNode = PathNode.extend({
            geometryChange: function () {
                var center = this.center();
                this.attr("cx", center.x);
                this.attr("cy", center.y);
                this.attr("r", this.radius());
                this.invalidate();
            },

            center: function () {
                return this.srcElement.geometry().center;
            },

            radius: function () {
                return this.srcElement.geometry().radius;
            },

            template: renderTemplate(
            "<circle #= d.renderStyle() # #= d.renderOpacity() # " +
            "cx='#= d.center().x #' cy='#= d.center().y #' " +
            "r='#= d.radius() #' " +
            "#= d.renderStroke() # " +
            "#= d.renderFill() # " +
            "#= d.renderDefinitions() # " +
            "#= d.renderTransform() # ></circle>"
        )
        });

        var TextNode = PathNode.extend({
            geometryChange: function () {
                var pos = this.pos();
                this.attr("x", pos.x);
                this.attr("y", pos.y);
                this.invalidate();
            },

            optionsChange: function (e) {
                if (e.field === "font") {
                    this.attr("style", util.renderStyle(this.mapStyle()));
                    this.geometryChange();
                } else if (e.field === "content") {
                    PathNode.fn.content.call(this, this.srcElement.content());
                }

                PathNode.fn.optionsChange.call(this, e);
            },

            mapStyle: function () {
                var style = PathNode.fn.mapStyle.call(this);
                var font = this.srcElement.options.font;
                var options = this.root().options;
                if (options && options.encodeText) {
                    font = kendo.htmlEncode(font);
                }

                style.push(["font", font]);

                return style;
            },

            pos: function () {
                var pos = this.srcElement.position();
                var size = this.srcElement.measure();
                return pos.clone().setY(pos.y + size.baseline);
            },

            content: function () {
                var content = this.srcElement.content();

                var options = this.root().options;
                if (options && options.encodeText) {
                    content = decodeEntities(content);
                    content = kendo.htmlEncode(content);
                }

                return content;
            },

            template: renderTemplate(
            "<text #= d.renderStyle() # #= d.renderOpacity() # " +
            "x='#= this.pos().x #' y='#= this.pos().y #' " +
            "#= d.renderStroke() # " +
            "#= d.renderTransform() # " +
            "#= d.renderDefinitions() # " +
            "#= d.renderFill() #><tspan>#= d.content() #</tspan></text>"
        )
        });

        var ImageNode = PathNode.extend({
            geometryChange: function () {
                this.allAttr(this.mapPosition());
                this.invalidate();
            },

            optionsChange: function (e) {
                if (e.field === "src") {
                    this.allAttr(this.mapSource());
                }

                PathNode.fn.optionsChange.call(this, e);
            },

            mapPosition: function () {
                var rect = this.srcElement.rect();
                var tl = rect.topLeft();

                return [
                ["x", tl.x],
                ["y", tl.y],
                ["width", rect.width() + "px"],
                ["height", rect.height() + "px"]
            ];
            },

            renderPosition: function () {
                return renderAllAttr(this.mapPosition());
            },

            mapSource: function () {
                return [["xlink:href", this.srcElement.src()]];
            },

            renderSource: function () {
                return renderAllAttr(this.mapSource());
            },

            template: renderTemplate(
            "<image preserveAspectRatio='none' #= d.renderStyle() # #= d.renderTransform()# #= d.renderOpacity() # " +
            "#= d.renderPosition() # #= d.renderSource() # #= d.renderDefinitions()#>" +
            "</image>"
        )
        });

        var GradientStopNode = Node.extend({
            template: renderTemplate(
            "<stop #=d.renderOffset()# #=d.renderStyle()# />"
        ),

            renderOffset: function () {
                return renderAttr("offset", this.srcElement.offset());
            },

            mapStyle: function () {
                var srcElement = this.srcElement;
                return [
                ["stop-color", srcElement.color()],
                ["stop-opacity", srcElement.opacity()]
            ];
            },

            optionsChange: function (e) {
                if (e.field == "offset") {
                    this.attr(e.field, e.value);
                } else if (e.field == "color" || e.field == "opacity") {
                    this.css("stop-" + e.field, e.value);
                }
            }
        });

        var GradientNode = Node.extend({
            init: function (srcElement) {
                Node.fn.init.call(this, srcElement);

                this.id = srcElement.id;

                this.loadStops();
            },

            loadStops: function () {
                var srcElement = this.srcElement;
                var stops = srcElement.stops;
                var element = this.element;
                var stopNode;
                var idx;
                for (idx = 0; idx < stops.length; idx++) {
                    stopNode = new GradientStopNode(stops[idx]);
                    this.append(stopNode);
                    if (element) {
                        stopNode.attachTo(element);
                    }
                }
            },

            optionsChange: function (e) {
                if (e.field == "gradient.stops") {
                    BaseNode.fn.clear.call(this);
                    this.loadStops();
                } else if (e.field == GRADIENT) {
                    this.allAttr(this.mapCoordinates());
                }
            },

            renderCoordinates: function () {
                return renderAllAttr(this.mapCoordinates());
            },

            mapSpace: function () {
                return ["gradientUnits", this.srcElement.userSpace() ? "userSpaceOnUse" : "objectBoundingBox"]
            }
        });

        var LinearGradientNode = GradientNode.extend({
            template: renderTemplate(
            "<linearGradient id='#=d.id#' #=d.renderCoordinates()#>" +
                "#= d.renderChildren()#" +
            "</linearGradient>"
        ),

            mapCoordinates: function () {
                var srcElement = this.srcElement;
                var start = srcElement.start();
                var end = srcElement.end();
                var attrs = [
                ["x1", start.x],
                ["y1", start.y],
                ["x2", end.x],
                ["y2", end.y],
                this.mapSpace()
            ];

                return attrs;
            }
        });

        var RadialGradientNode = GradientNode.extend({
            template: renderTemplate(
            "<radialGradient id='#=d.id#' #=d.renderCoordinates()#>" +
                "#= d.renderChildren()#" +
            "</radialGradient>"
        ),

            mapCoordinates: function () {
                var srcElement = this.srcElement;
                var center = srcElement.center();
                var radius = srcElement.radius();
                var attrs = [
                ["cx", center.x],
                ["cy", center.y],
                ["r", radius],
                this.mapSpace()
            ];
                return attrs;
            }
        });

        var nodeMap = {
            Group: GroupNode,
            Text: TextNode,
            Path: PathNode,
            MultiPath: MultiPathNode,
            Circle: CircleNode,
            Arc: ArcNode,
            Image: ImageNode
        };

        // Helpers ================================================================
        var renderSVG = function (container, svg) {
            container.innerHTML = svg;
        };

        (function () {
            var testFragment = "<svg xmlns='" + SVG_NS + "'></svg>",
            testContainer = doc.createElement("div"),
            hasParser = typeof DOMParser != UNDEFINED;

            testContainer.innerHTML = testFragment;

            if (hasParser && testContainer.firstChild.namespaceURI != SVG_NS) {
                renderSVG = function (container, svg) {
                    var parser = new DOMParser(),
                    chartDoc = parser.parseFromString(svg, "text/xml"),
                    importedDoc = doc.adoptNode(chartDoc.documentElement);

                    container.innerHTML = "";
                    container.appendChild(importedDoc);
                };
            }
        })();

        function alignToScreen(element) {
            var ctm;

            try {
                ctm = element.getScreenCTM ? element.getScreenCTM() : null;
            } catch (e) { }

            if (ctm) {
                var left = -ctm.e % 1,
                top = -ctm.f % 1,
                style = element.style;

                if (left !== 0 || top !== 0) {
                    style.left = left + "px";
                    style.top = top + "px";
                }
            }
        }

        function baseUrl() {
            var base = document.getElementsByTagName("base")[0],
            url = "",
            href = document.location.href,
            hashIndex = href.indexOf("#");

            if (base && !kendo.support.browser.msie) {
                if (hashIndex !== -1) {
                    href = href.substring(0, hashIndex);
                }

                url = href;
            }

            return url;
        }

        function refUrl(id) {
            return "url(" + baseUrl() + "#" + id + ")";
        }

        function exportGroup(group) {
            var root = new RootNode({ encodeText: true });

            var bbox = group.clippedBBox();
            if (bbox) {
                var origin = bbox.getOrigin();
                var exportRoot = new d.Group();
                exportRoot.transform(g.transform().translate(-origin.x, -origin.y));
                exportRoot.children.push(group);
                group = exportRoot;
            }

            root.load([group]);

            var svg = "<?xml version='1.0' ?>" +
                  "<svg style='width: 100%; height: 100%; overflow: hidden;' " +
                  "xmlns='" + SVG_NS + "' " + "xmlns:xlink='http://www.w3.org/1999/xlink' " +
                  "version='1.1'>" + root.render() + "</svg>";

            root.destroy();

            return svg;
        }

        function exportSVG(group, options) {
            var svg = exportGroup(group);

            if (!options || !options.raw) {
                svg = "data:image/svg+xml;base64," + util.encodeBase64(svg);
            }

            return $.Deferred().resolve(svg).promise();
        }

        function isDefinition(type, value) {
            return type == "clip" || (type == "fill" && (!value || value.nodeType == GRADIENT));
        }

        // Mappings ===============================================================

        function decodeEntities(text) {
            if (!text || !text.indexOf || text.indexOf("&") < 0) {
                return text;
            } else {
                var element = decodeEntities._element;
                element.innerHTML = text;
                return element.textContent || element.innerText;
            }
        }

        decodeEntities._element = document.createElement("span");

        // Mappings ===============================================================
        var DefinitionMap = {
            clip: "clip-path",
            fill: "fill"
        };

        // Exports ================================================================
        kendo.support.svg = (function () {
            return doc.implementation.hasFeature(
            "http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1");
        })();

        if (kendo.support.svg) {
            d.SurfaceFactory.current.register("svg", Surface, 10);
        }

        deepExtend(d, {
            exportSVG: exportSVG,

            svg: {
                ArcNode: ArcNode,
                CircleNode: CircleNode,
                ClipNode: ClipNode,
                DefinitionNode: DefinitionNode,
                GradientStopNode: GradientStopNode,
                GroupNode: GroupNode,
                ImageNode: ImageNode,
                LinearGradientNode: LinearGradientNode,
                MultiPathNode: MultiPathNode,
                Node: Node,
                PathNode: PathNode,
                RadialGradientNode: RadialGradientNode,
                RootNode: RootNode,
                Surface: Surface,
                TextNode: TextNode,
                _exportGroup: exportGroup
            }
        });

    })(window.kendo.jQuery);

    (function ($) {

        // Imports ================================================================
        var noop = $.noop,
        doc = document,

        kendo = window.kendo,
        deepExtend = kendo.deepExtend,

        util = kendo.util,
        defined = util.defined,
        isTransparent = util.isTransparent,
        renderTemplate = util.renderTemplate,
        valueOrDefault = util.valueOrDefault,

        g = kendo.geometry,
        d = kendo.drawing,
        BaseNode = d.BaseNode;

        // Constants ==============================================================
        var BUTT = "butt",
        DASH_ARRAYS = d.DASH_ARRAYS,
        FRAME_DELAY = 1000 / 60,
        NONE = "none",
        SOLID = "solid";

        // Canvas Surface ==========================================================
        var Surface = d.Surface.extend({
            init: function (element, options) {
                d.Surface.fn.init.call(this, element, options);

                this.element[0].innerHTML = this._template(this);
                var canvas = this.element[0].firstElementChild;
                canvas.width = $(element).width();
                canvas.height = $(element).height();
                this._rootElement = canvas;

                this._root = new RootNode(canvas);
            },

            destroy: function () {
                d.Surface.fn.destroy.call(this);

                if (this._root) {
                    this._root.destroy();
                    this._root = null;
                }
            },

            type: "canvas",

            draw: function (element) {
                this._root.load([element], undefined, this.options.cors);
            },

            clear: function () {
                this._root.clear();
            },

            image: function () {
                var root = this._root;
                var rootElement = this._rootElement;

                var loadingStates = [];
                root.traverse(function (childNode) {
                    if (childNode.loading) {
                        loadingStates.push(childNode.loading);
                    }
                });

                var defer = $.Deferred();
                $.when.apply($, loadingStates).done(function () {
                    root._invalidate();

                    try {
                        var data = rootElement.toDataURL();
                        defer.resolve(data);
                    } catch (e) {
                        defer.reject(e);
                    }
                }).fail(function (e) {
                    defer.reject(e);
                });

                return defer.promise();
            },

            _resize: function () {
                this._rootElement.width = this._size.width;
                this._rootElement.height = this._size.height;

                this._root.invalidate();
            },

            _template: renderTemplate(
            "<canvas style='width: 100%; height: 100%;'></canvas>"
        )
        });

        // Nodes ===================================================================
        var Node = BaseNode.extend({
            init: function (srcElement) {
                BaseNode.fn.init.call(this, srcElement);
                if (srcElement) {
                    this.initClip();
                }
            },

            initClip: function () {
                var clip = this.srcElement.clip();
                if (clip) {
                    this.clip = clip;
                    clip.addObserver(this);
                }
            },

            clear: function () {
                if (this.srcElement) {
                    this.srcElement.removeObserver(this);
                }

                this.clearClip();

                BaseNode.fn.clear.call(this);
            },

            clearClip: function () {
                if (this.clip) {
                    this.clip.removeObserver(this);
                    delete this.clip;
                }
            },

            setClip: function (ctx) {
                if (this.clip) {
                    ctx.beginPath();
                    PathNode.fn.renderPoints(ctx, this.clip);
                    ctx.clip();
                }
            },

            optionsChange: function (e) {
                if (e.field == "clip") {
                    this.clearClip();
                    this.initClip();
                }

                BaseNode.fn.optionsChange.call(this, e);
            },

            setTransform: function (ctx) {
                if (this.srcElement) {
                    var transform = this.srcElement.transform();
                    if (transform) {
                        ctx.transform.apply(ctx, transform.matrix().toArray(6));
                    }
                }
            },

            load: function (elements, pos, cors) {
                var node = this,
                childNode,
                srcElement,
                children,
                i;

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

                    childNode = new nodeMap[srcElement.nodeType](srcElement, cors);

                    if (children && children.length > 0) {
                        childNode.load(children, pos, cors);
                    }

                    if (defined(pos)) {
                        node.insertAt(childNode, pos);
                    } else {
                        node.append(childNode);
                    }
                }

                node.invalidate();
            },

            setOpacity: function (ctx) {
                if (this.srcElement) {
                    var opacity = this.srcElement.opacity();
                    if (defined(opacity)) {
                        this.globalAlpha(ctx, opacity);
                    }
                }
            },

            globalAlpha: function (ctx, value) {
                if (value && ctx.globalAlpha) {
                    value *= ctx.globalAlpha;
                }
                ctx.globalAlpha = value;
            }
        });

        var GroupNode = Node.extend({
            renderTo: function (ctx) {
                var childNodes = this.childNodes,
                i;

                ctx.save();

                this.setTransform(ctx);
                this.setClip(ctx);
                this.setOpacity(ctx);

                for (i = 0; i < childNodes.length; i++) {
                    childNodes[i].renderTo(ctx);
                }

                ctx.restore();
            }
        });
        d.mixins.Traversable.extend(GroupNode.fn, "childNodes");

        var RootNode = GroupNode.extend({
            init: function (canvas) {
                GroupNode.fn.init.call(this);

                this.canvas = canvas;
                this.ctx = canvas.getContext("2d");

                this.invalidate = kendo.throttle(
                $.proxy(this._invalidate, this),
                FRAME_DELAY
            );
            },

            destroy: function () {
                GroupNode.fn.destroy.call(this);
                this.canvas = null;
                this.ctx = null;
            },

            _invalidate: function () {
                if (!this.ctx) {
                    return;
                }

                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                this.renderTo(this.ctx);
            }
        });
        d.mixins.Traversable.extend(RootNode.fn, "childNodes");

        var PathNode = Node.extend({
            renderTo: function (ctx) {
                ctx.save();

                this.setTransform(ctx);
                this.setClip(ctx);
                this.setOpacity(ctx);

                ctx.beginPath();

                this.renderPoints(ctx, this.srcElement);

                this.setLineDash(ctx);
                this.setLineCap(ctx);
                this.setLineJoin(ctx);

                this.setFill(ctx);
                this.setStroke(ctx);

                ctx.restore();
            },

            setFill: function (ctx) {
                var fill = this.srcElement.options.fill;
                var hasFill = false;

                if (fill) {
                    if (fill.nodeType == "gradient") {
                        this.setGradientFill(ctx, fill);
                        hasFill = true;
                    } else if (!isTransparent(fill.color)) {
                        ctx.fillStyle = fill.color;

                        ctx.save();
                        this.globalAlpha(ctx, fill.opacity);
                        ctx.fill();
                        ctx.restore();

                        hasFill = true;
                    }
                }

                return hasFill;
            },

            setGradientFill: function (ctx, fill) {
                var bbox = this.srcElement.rawBBox();
                var gradient;

                if (fill instanceof d.LinearGradient) {
                    var start = fill.start();
                    var end = fill.end();
                    gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
                } else if (fill instanceof d.RadialGradient) {
                    var center = fill.center();
                    gradient = ctx.createRadialGradient(center.x, center.y, 0, center.x, center.y, fill.radius());
                }

                addGradientStops(gradient, fill.stops);

                ctx.save();

                if (!fill.userSpace()) {
                    ctx.transform(bbox.width(), 0, 0, bbox.height(), bbox.origin.x, bbox.origin.y);
                }
                ctx.fillStyle = gradient;
                ctx.fill();

                ctx.restore();
            },

            setStroke: function (ctx) {
                var stroke = this.srcElement.options.stroke;
                if (stroke && !isTransparent(stroke.color)) {
                    ctx.strokeStyle = stroke.color;
                    ctx.lineWidth = valueOrDefault(stroke.width, 1);

                    ctx.save();
                    this.globalAlpha(ctx, stroke.opacity);
                    ctx.stroke();
                    ctx.restore();

                    return true;
                }
            },

            dashType: function () {
                var stroke = this.srcElement.options.stroke;
                if (stroke && stroke.dashType) {
                    return stroke.dashType.toLowerCase();
                }
            },

            setLineDash: function (ctx) {
                var dashType = this.dashType();
                if (dashType && dashType != SOLID) {
                    var dashArray = DASH_ARRAYS[dashType];
                    if (ctx.setLineDash) {
                        ctx.setLineDash(dashArray);
                    } else {
                        ctx.mozDash = dashArray;
                        ctx.webkitLineDash = dashArray;
                    }
                }
            },

            setLineCap: function (ctx) {
                var dashType = this.dashType();
                var stroke = this.srcElement.options.stroke;
                if (dashType && dashType !== SOLID) {
                    ctx.lineCap = BUTT;
                } else if (stroke && stroke.lineCap) {
                    ctx.lineCap = stroke.lineCap;
                }
            },

            setLineJoin: function (ctx) {
                var stroke = this.srcElement.options.stroke;
                if (stroke && stroke.lineJoin) {
                    ctx.lineJoin = stroke.lineJoin;
                }
            },

            renderPoints: function (ctx, path) {
                var segments = path.segments;

                if (segments.length === 0) {
                    return;
                }

                var seg = segments[0];
                var anchor = seg.anchor();
                ctx.moveTo(anchor.x, anchor.y);

                for (var i = 1; i < segments.length; i++) {
                    seg = segments[i];
                    anchor = seg.anchor();

                    var prevSeg = segments[i - 1];
                    var prevOut = prevSeg.controlOut();
                    var controlIn = seg.controlIn();

                    if (prevOut && controlIn) {
                        ctx.bezierCurveTo(prevOut.x, prevOut.y,
                                      controlIn.x, controlIn.y,
                                      anchor.x, anchor.y);
                    } else {
                        ctx.lineTo(anchor.x, anchor.y);
                    }
                }

                if (path.options.closed) {
                    ctx.closePath();
                }
            }
        });

        var MultiPathNode = PathNode.extend({
            renderPoints: function (ctx) {
                var paths = this.srcElement.paths;
                for (var i = 0; i < paths.length; i++) {
                    PathNode.fn.renderPoints(ctx, paths[i]);
                }
            }
        });

        var CircleNode = PathNode.extend({
            renderPoints: function (ctx) {
                var geometry = this.srcElement.geometry();
                var c = geometry.center;
                var r = geometry.radius;

                ctx.arc(c.x, c.y, r, 0, Math.PI * 2);
            }
        });

        var ArcNode = PathNode.extend({
            renderPoints: function (ctx) {
                var path = this.srcElement.toPath();
                PathNode.fn.renderPoints.call(this, ctx, path);
            }
        });

        var TextNode = PathNode.extend({
            renderTo: function (ctx) {
                var text = this.srcElement;
                var pos = text.position();
                var size = text.measure();

                ctx.save();

                this.setTransform(ctx);
                this.setClip(ctx);
                this.setOpacity(ctx);

                ctx.beginPath();

                ctx.font = text.options.font;
                if (this.setFill(ctx)) {
                    ctx.fillText(text.content(), pos.x, pos.y + size.baseline);
                }

                if (this.setStroke(ctx)) {
                    this.setLineDash(ctx);
                    ctx.strokeText(text.content(), pos.x, pos.y + size.baseline);
                }

                ctx.restore();
            }
        });

        var ImageNode = PathNode.extend({
            init: function (srcElement, cors) {
                PathNode.fn.init.call(this, srcElement);

                this.onLoad = $.proxy(this.onLoad, this);
                this.onError = $.proxy(this.onError, this);

                var img = this.img = new Image();

                if (cors) {
                    img.crossOrigin = cors;
                }

                img.onload = this.onLoad;
                img.onerror = this.onError;
                var src = img.src = srcElement.src();

                // Make sure the load event fires for cached images too
                // See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
                if (img.complete || img.complete === undefined) {
                    img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
                    img.src = src;
                }

                this.loading = $.Deferred();
            },

            renderTo: function (ctx) {
                if (this.loading.state() === "resolved") {
                    ctx.save();

                    this.setTransform(ctx);
                    this.setClip(ctx);

                    this.drawImage(ctx);

                    ctx.restore();
                }
            },

            optionsChange: function (e) {
                if (e.field === "src") {
                    this.loading = $.Deferred();
                    this.img.src = this.srcElement.src();
                } else {
                    PathNode.fn.optionsChange.call(this, e);
                }
            },

            onLoad: function () {
                this.loading.resolve();
                this.invalidate();
            },

            onError: function () {
                this.loading.reject(new Error(
                "Unable to load image '" + this.img.src +
                "'. Check for connectivity and verify CORS headers."
            ));
            },

            drawImage: function (ctx) {
                var rect = this.srcElement.rect();
                var tl = rect.topLeft();

                ctx.drawImage(
                this.img, tl.x, tl.y, rect.width(), rect.height()
            );
            }
        });

        function exportImage(group, options) {
            var defaults = {
                width: "800px", height: "600px",
                cors: "Anonymous"
            };

            var bbox = group.clippedBBox();
            if (bbox) {
                var origin = bbox.getOrigin();
                var exportRoot = new d.Group();
                exportRoot.transform(g.transform().translate(-origin.x, -origin.y));
                exportRoot.children.push(group);
                group = exportRoot;

                var size = bbox.getSize();
                defaults.width = size.width + "px";
                defaults.height = size.height + "px";
            }

            options = deepExtend(defaults, options);

            var container = $("<div />").css({
                display: "none",
                width: options.width,
                height: options.height
            }).appendTo(document.body);

            var surface = new Surface(container, options);
            surface.draw(group);

            var promise = surface.image();
            promise.always(function () {
                surface.destroy();
                container.remove();
            });

            return promise;
        }

        var nodeMap = {
            Group: GroupNode,
            Text: TextNode,
            Path: PathNode,
            MultiPath: MultiPathNode,
            Circle: CircleNode,
            Arc: ArcNode,
            Image: ImageNode
        };

        // Helpers ================================================================
        function addGradientStops(gradient, stops) {
            var color, stop, idx;

            for (idx = 0; idx < stops.length; idx++) {
                stop = stops[idx];
                color = kendo.parseColor(stop.color());
                color.a *= stop.opacity();
                gradient.addColorStop(stop.offset(), color.toCssRgba());
            }
        }

        // Exports ================================================================
        kendo.support.canvas = (function () {
            return !!doc.createElement("canvas").getContext;
        })();

        if (kendo.support.canvas) {
            d.SurfaceFactory.current.register("canvas", Surface, 20);
        }

        deepExtend(kendo.drawing, {
            exportImage: exportImage,

            canvas: {
                ArcNode: ArcNode,
                CircleNode: CircleNode,
                GroupNode: GroupNode,
                ImageNode: ImageNode,
                MultiPathNode: MultiPathNode,
                Node: Node,
                PathNode: PathNode,
                RootNode: RootNode,
                Surface: Surface,
                TextNode: TextNode
            }
        });

    })(window.kendo.jQuery);

    (function ($) {

        // Imports ================================================================
        var doc = document,
        math = Math,
        atan2 = math.atan2,
        sqrt = math.sqrt,

        kendo = window.kendo,
        deepExtend = kendo.deepExtend,
        noop = $.noop,

        d = kendo.drawing,
        BaseNode = d.BaseNode,

        g = kendo.geometry,
        toMatrix = g.toMatrix,

        Color = kendo.Color,

        util = kendo.util,
        isTransparent = util.isTransparent,
        defined = util.defined,
        deg = util.deg,
        renderTemplate = util.renderTemplate,
        round = util.round,
        valueOrDefault = util.valueOrDefault;

        // Constants ==============================================================
        var NONE = "none",
        NS = ".kendo",
        COORDINATE_MULTIPLE = 100,
        COORDINATE_SIZE = COORDINATE_MULTIPLE * COORDINATE_MULTIPLE,
        GRADIENT = "gradient",
        TRANSFORM_PRECISION = 4;

        // VML rendering surface ==================================================
        var Surface = d.Surface.extend({
            init: function (element, options) {
                d.Surface.fn.init.call(this, element, options);

                enableVML();

                this.element.empty();

                this._root = new RootNode();
                this._root.attachTo(this.element[0]);

                this.element.on("click" + NS, this._click);
                this.element.on("mouseover" + NS, this._mouseenter);
                this.element.on("mouseout" + NS, this._mouseleave);
            },

            type: "vml",

            destroy: function () {
                if (this._root) {
                    this._root.destroy();
                    this._root = null;

                    this.element.off(NS);
                }

                d.Surface.fn.destroy.call(this);
            },

            draw: function (element) {
                this._root.load([element], undefined, null);
            },

            clear: function () {
                this._root.clear();
            }
        });

        // VML Node ================================================================
        var Node = BaseNode.extend({
            init: function (srcElement) {
                BaseNode.fn.init.call(this, srcElement);

                this.createElement();
                this.attachReference();
            },

            observe: noop,

            destroy: function () {
                if (this.element) {
                    this.element._kendoNode = null;
                    this.element = null;
                }

                BaseNode.fn.destroy.call(this);
            },

            clear: function () {
                if (this.element) {
                    this.element.innerHTML = "";
                }

                var children = this.childNodes;
                for (var i = 0; i < children.length; i++) {
                    children[i].destroy();
                }

                this.childNodes = [];
            },

            removeSelf: function () {
                if (this.element) {
                    this.element.parentNode.removeChild(this.element);
                    this.element = null;
                }

                BaseNode.fn.removeSelf.call(this);
            },

            createElement: function () {
                this.element = doc.createElement("div");
            },

            attachReference: function () {
                this.element._kendoNode = this;
            },

            load: function (elements, pos, transform, opacity) {
                opacity = valueOrDefault(opacity, 1);
                if (this.srcElement) {
                    opacity *= valueOrDefault(this.srcElement.options.opacity, 1);
                }

                for (var i = 0; i < elements.length; i++) {
                    var srcElement = elements[i];
                    var children = srcElement.children;
                    var combinedTransform = srcElement.currentTransform(transform);
                    var currentOpacity = opacity * valueOrDefault(srcElement.options.opacity, 1);

                    var childNode = new nodeMap[srcElement.nodeType](srcElement, combinedTransform, currentOpacity);

                    if (children && children.length > 0) {
                        childNode.load(children, pos, combinedTransform, opacity);
                    }

                    if (defined(pos)) {
                        this.insertAt(childNode, pos);
                    } else {
                        this.append(childNode);
                    }

                    childNode.attachTo(this.element, pos);
                }
            },

            attachTo: function (domElement, pos) {
                if (defined(pos)) {
                    domElement.insertBefore(this.element, domElement.children[pos] || null);
                } else {
                    domElement.appendChild(this.element);
                }
            },

            optionsChange: function (e) {
                if (e.field == "visible") {
                    this.css("display", e.value !== false ? "" : NONE);
                }
            },

            setStyle: function () {
                this.allCss(this.mapStyle());
            },

            mapStyle: function () {
                var style = [];

                if (this.srcElement && this.srcElement.options.visible === false) {
                    style.push(["display", NONE]);
                }

                return style;
            },

            mapOpacityTo: function (attrs, multiplier) {
                var opacity = valueOrDefault(this.opacity, 1);

                opacity *= valueOrDefault(multiplier, 1);
                attrs.push(["opacity", opacity]);
            },

            attr: function (name, value) {
                if (this.element) {
                    this.element[name] = value;
                }
            },

            allAttr: function (attrs) {
                for (var i = 0; i < attrs.length; i++) {
                    this.attr(attrs[i][0], attrs[i][1]);
                }
            },

            css: function (name, value) {
                if (this.element) {
                    this.element.style[name] = value;
                }
            },

            allCss: function (styles) {
                for (var i = 0; i < styles.length; i++) {
                    this.css(styles[i][0], styles[i][1]);
                }
            }
        });

        var RootNode = Node.extend({
            createElement: function () {
                Node.fn.createElement.call(this);

                this.allCss([
                ["width", "100%"],
                ["height", "100%"],
                ["position", "relative"],
                ["visibility", "visible"]
            ]);
            },

            attachReference: noop
        });

        var ClipObserver = kendo.Class.extend({
            init: function (srcElement, observer) {
                this.srcElement = srcElement;
                this.observer = observer;

                srcElement.addObserver(this);
            },

            geometryChange: function () {
                this.observer.optionsChange({
                    field: "clip",
                    value: this.srcElement
                });
            },

            clear: function () {
                this.srcElement.removeObserver(this);
            }
        });

        var ObserverNode = Node.extend({
            init: function (srcElement) {
                Node.fn.init.call(this, srcElement);

                if (srcElement) {
                    this.initClip();
                }
            },

            observe: function () {
                BaseNode.fn.observe.call(this);
            },

            mapStyle: function () {
                var style = Node.fn.mapStyle.call(this);
                if (this.srcElement && this.srcElement.clip()) {
                    style.push(["clip", this.clipRect()]);
                }
                return style;
            },

            optionsChange: function (e) {
                if (e.field == "clip") {
                    this.clearClip();
                    this.initClip();
                    this.setClip();
                }

                Node.fn.optionsChange.call(this, e);
            },

            clear: function () {
                this.clearClip();

                Node.fn.clear.call(this);
            },

            initClip: function () {
                if (this.srcElement.clip()) {
                    this.clip = new ClipObserver(this.srcElement.clip(), this);
                    this.clip.observer = this;
                }
            },

            clearClip: function () {
                if (this.clip) {
                    this.clip.clear();
                    this.clip = null;
                    this.css("clip", this.clipRect());
                }
            },

            setClip: function () {
                if (this.clip) {
                    this.css("clip", this.clipRect());
                }
            },

            clipRect: function () {
                var clipRect = EMPTY_CLIP;
                var clip = this.srcElement.clip();
                if (clip) {
                    var bbox = this.clipBBox(clip);
                    var topLeft = bbox.topLeft();
                    var bottomRight = bbox.bottomRight();
                    clipRect = kendo.format("rect({0}px {1}px {2}px {3}px)",
                    topLeft.y,
                    bottomRight.x,
                    bottomRight.y,
                    topLeft.x);
                }
                return clipRect;
            },

            clipBBox: function (clip) {
                var topLeft = this.srcElement.rawBBox().topLeft();
                var clipBBox = clip.rawBBox();
                clipBBox.origin.translate(-topLeft.x, -topLeft.y);

                return clipBBox;
            }
        });

        var GroupNode = ObserverNode.extend({
            createElement: function () {
                Node.fn.createElement.call(this);
                this.setStyle();
            },

            attachTo: function (domElement, pos) {
                this.css("display", NONE);

                Node.fn.attachTo.call(this, domElement, pos);

                if (this.srcElement.options.visible !== false) {
                    this.css("display", "");
                }
            },

            _attachTo: function (domElement) {
                var frag = document.createDocumentFragment();
                frag.appendChild(this.element);

                domElement.appendChild(frag);
            },

            mapStyle: function () {
                var style = ObserverNode.fn.mapStyle.call(this);
                style.push(["position", "absolute"]);
                style.push(["white-space", "nowrap"]);

                return style;
            },

            optionsChange: function (e) {
                if (e.field === "transform") {
                    this.refreshTransform();
                }

                if (e.field === "opacity") {
                    this.refreshOpacity();
                }

                ObserverNode.fn.optionsChange.call(this, e);
            },

            refreshTransform: function (transform) {
                var currentTransform = this.srcElement.currentTransform(transform),
                children = this.childNodes,
                length = children.length,
                i;

                this.setClip();
                for (i = 0; i < length; i++) {
                    children[i].refreshTransform(currentTransform);
                }
            },

            currentOpacity: function () {
                var opacity = valueOrDefault(this.srcElement.options.opacity, 1);

                if (this.parent && this.parent.currentOpacity) {
                    opacity *= this.parent.currentOpacity();
                }

                return opacity;
            },

            refreshOpacity: function () {
                var children = this.childNodes,
                length = children.length,
                i;

                var opacity = this.currentOpacity();
                for (i = 0; i < length; i++) {
                    children[i].refreshOpacity(opacity);
                }
            },

            initClip: function () {
                ObserverNode.fn.initClip.call(this);

                if (this.clip) {
                    var bbox = this.clip.srcElement.bbox(this.srcElement.currentTransform());
                    if (bbox) {
                        this.css("width", bbox.width() + bbox.origin.x);
                        this.css("height", bbox.height() + bbox.origin.y);
                    }
                }
            },

            clipBBox: function (clip) {
                return clip.bbox(this.srcElement.currentTransform());
            },

            clearClip: function () {
                ObserverNode.fn.clearClip.call(this);
            }
        });

        var StrokeNode = Node.extend({
            init: function (srcElement, opacity) {
                this.opacity = opacity;
                Node.fn.init.call(this, srcElement);
            },

            createElement: function () {
                this.element = createElementVML("stroke");
                this.setOpacity();
            },

            optionsChange: function (e) {
                if (e.field.indexOf("stroke") === 0) {
                    this.setStroke();
                }
            },

            refreshOpacity: function (opacity) {
                this.opacity = opacity;
                this.setStroke();
            },

            setStroke: function () {
                this.allAttr(this.mapStroke());
            },

            setOpacity: function () {
                this.setStroke();
            },

            mapStroke: function () {
                var stroke = this.srcElement.options.stroke;
                var attrs = [];

                if (stroke && !isTransparent(stroke.color) && stroke.width !== 0) {
                    attrs.push(["on", "true"]);
                    attrs.push(["color", stroke.color]);
                    attrs.push(["weight", (stroke.width || 1) + "px"]);

                    this.mapOpacityTo(attrs, stroke.opacity);

                    if (defined(stroke.dashType)) {
                        attrs.push(["dashstyle", stroke.dashType]);
                    }

                    if (defined(stroke.lineJoin)) {
                        attrs.push(["joinstyle", stroke.lineJoin]);
                    }

                    if (defined(stroke.lineCap)) {
                        var lineCap = stroke.lineCap.toLowerCase();
                        if (lineCap === "butt") {
                            lineCap = lineCap === "butt" ? "flat" : lineCap;
                        }
                        attrs.push(["endcap", lineCap]);
                    }
                } else {
                    attrs.push(["on", "false"]);
                }

                return attrs;
            }
        });

        var FillNode = Node.extend({
            init: function (srcElement, transform, opacity) {
                this.opacity = opacity;
                Node.fn.init.call(this, srcElement);
            },

            createElement: function () {
                this.element = createElementVML("fill");
                this.setFill();
            },

            optionsChange: function (e) {
                if (fillField(e.field)) {
                    this.setFill();
                }
            },

            refreshOpacity: function (opacity) {
                this.opacity = opacity;
                this.setOpacity();
            },

            setFill: function () {
                this.allAttr(this.mapFill());
            },

            setOpacity: function () {
                this.setFill();
            },

            attr: function (name, value) {
                var element = this.element;
                if (element) {
                    var fields = name.split(".");

                    while (fields.length > 1) {
                        element = element[fields.shift()];
                    }
                    element[fields[0]] = value;
                }
            },

            mapFill: function () {
                var fill = this.srcElement.fill();
                var attrs = [
                ["on", "false"]
            ];

                if (fill) {
                    if (fill.nodeType == GRADIENT) {
                        attrs = this.mapGradient(fill);
                    } else if (!isTransparent(fill.color)) {
                        attrs = this.mapFillColor(fill);
                    }
                }

                return attrs;
            },

            mapFillColor: function (fill) {
                var attrs = [
                ["on", "true"],
                ["color", fill.color]
            ];

                this.mapOpacityTo(attrs, fill.opacity);

                return attrs;
            },

            mapGradient: function (fill) {
                var options = this.srcElement.options;
                var fallbackFill = options.fallbackFill || (fill.fallbackFill && fill.fallbackFill());
                var attrs;
                if (fill instanceof d.LinearGradient) {
                    attrs = this.mapLinearGradient(fill);
                } else if (fill instanceof d.RadialGradient && fill.supportVML) {
                    attrs = this.mapRadialGradient(fill);
                } else if (fallbackFill) {
                    attrs = this.mapFillColor(fallbackFill);
                } else {
                    attrs = [["on", "false"]];
                }

                return attrs;
            },

            mapLinearGradient: function (fill) {
                var start = fill.start();
                var end = fill.end();
                var stops = fill.stops;
                var angle = util.deg(atan2(end.y - start.y, end.x - start.x));

                var attrs = [
                ["on", "true"],
                ["type", GRADIENT],
                ["focus", 0],
                ["method", "none"],
                ["angle", 270 - angle]
            ];
                this.addColors(attrs);
                return attrs;
            },

            mapRadialGradient: function (fill) {
                var bbox = this.srcElement.rawBBox();
                var center = fill.center();
                var stops = fill.stops;
                var focusx = (center.x - bbox.origin.x) / bbox.width();
                var focusy = (center.y - bbox.origin.y) / bbox.height();
                var attrs = [
                ["on", "true"],
                ["type", "gradienttitle"],
                ["focus", "100%"],
                ["focusposition", focusx + " " + focusy],
                ["method", "none"]
            ];
                this.addColors(attrs);

                return attrs;
            },

            addColors: function (attrs) {
                var options = this.srcElement.options;
                var stopColors = [];
                var stops = options.fill.stops;
                var baseColor = options.baseColor;
                var colorsField = this.element.colors ? "colors.value" : "colors";
                var color = stopColor(baseColor, stops[0]);
                var color2 = stopColor(baseColor, stops[stops.length - 1]);
                var stop;

                for (var idx = 0; idx < stops.length; idx++) {
                    stop = stops[idx];

                    stopColors.push(
                    math.round(stop.offset() * 100) + "% " +
                    stopColor(baseColor, stop)
                );
                }

                attrs.push([colorsField, stopColors.join(",")],
                ["color", color],
                ["color2", color2]
            );
            }
        });

        var TransformNode = Node.extend({
            init: function (srcElement, transform) {
                this.transform = transform;

                Node.fn.init.call(this, srcElement);
            },

            createElement: function () {
                this.element = createElementVML("skew");
                this.setTransform();
            },

            optionsChange: function (e) {
                if (e.field === "transform") {
                    this.refresh(this.srcElement.currentTransform());
                }
            },

            refresh: function (transform) {
                this.transform = transform;
                this.setTransform();
            },

            transformOrigin: function () {
                return "-0.5,-0.5";
            },

            setTransform: function () {
                this.allAttr(this.mapTransform());
            },

            mapTransform: function () {
                var transform = this.transform;

                var attrs = [],
                a, b, c, d,
                matrix = toMatrix(transform);

                if (matrix) {
                    matrix.round(TRANSFORM_PRECISION);
                    attrs.push(
                    ["on", "true"],
                    ["matrix", [matrix.a, matrix.c, matrix.b, matrix.d, 0, 0].join(",")],
                    ["offset", matrix.e + "px," + matrix.f + "px"],
                    ["origin", this.transformOrigin()]
                );
                } else {
                    attrs.push(["on", "false"]);
                }

                return attrs;
            }
        });

        var ShapeNode = ObserverNode.extend({
            init: function (srcElement, transform, opacity) {
                this.fill = this.createFillNode(srcElement, transform, opacity);
                this.stroke = new StrokeNode(srcElement, opacity);
                this.transform = this.createTransformNode(srcElement, transform);

                ObserverNode.fn.init.call(this, srcElement);
            },

            attachTo: function (domElement, pos) {
                this.fill.attachTo(this.element);
                this.stroke.attachTo(this.element);
                this.transform.attachTo(this.element);

                Node.fn.attachTo.call(this, domElement, pos);
            },

            createFillNode: function (srcElement, transform, opacity) {
                return new FillNode(srcElement, transform, opacity);
            },

            createTransformNode: function (srcElement, transform) {
                return new TransformNode(srcElement, transform);
            },

            createElement: function () {
                this.element = createElementVML("shape");
                this.setCoordsize();
                this.setStyle();
            },

            optionsChange: function (e) {
                if (fillField(e.field)) {
                    this.fill.optionsChange(e);
                } else if (e.field.indexOf("stroke") === 0) {
                    this.stroke.optionsChange(e);
                } else if (e.field === "transform") {
                    this.transform.optionsChange(e);
                } else if (e.field === "opacity") {
                    this.fill.setOpacity();
                    this.stroke.setOpacity();
                }

                ObserverNode.fn.optionsChange.call(this, e);
            },

            refreshTransform: function (transform) {
                this.transform.refresh(this.srcElement.currentTransform(transform));
            },

            refreshOpacity: function (opacity) {
                opacity *= valueOrDefault(this.srcElement.options.opacity, 1);

                this.fill.refreshOpacity(opacity);
                this.stroke.refreshOpacity(opacity);
            },

            mapStyle: function (width, height) {
                var styles = ObserverNode.fn.mapStyle.call(this);

                if (!width || !height) {
                    width = height = COORDINATE_MULTIPLE;
                }

                styles.push(
                ["position", "absolute"],
                ["width", width + "px"],
                ["height", height + "px"]
            );

                var cursor = this.srcElement.options.cursor;
                if (cursor) {
                    styles.push(["cursor", cursor]);
                }

                return styles;
            },

            setCoordsize: function () {
                this.allAttr([
                ["coordorigin", "0 0"],
                ["coordsize", COORDINATE_SIZE + " " + COORDINATE_SIZE]
            ]);
            }
        });

        var PathDataNode = Node.extend({
            createElement: function () {
                this.element = createElementVML("path");
                this.setPathData();
            },

            geometryChange: function () {
                this.setPathData();
            },

            setPathData: function () {
                this.attr("v", this.renderData());
            },

            renderData: function () {
                return printPath(this.srcElement);
            }
        });

        var PathNode = ShapeNode.extend({
            init: function (srcElement, transform, opacity) {
                this.pathData = this.createDataNode(srcElement);

                ShapeNode.fn.init.call(this, srcElement, transform, opacity);
            },

            attachTo: function (domElement, pos) {
                this.pathData.attachTo(this.element);
                ShapeNode.fn.attachTo.call(this, domElement, pos);
            },

            createDataNode: function (srcElement) {
                return new PathDataNode(srcElement);
            },

            geometryChange: function () {
                this.pathData.geometryChange();
                ShapeNode.fn.geometryChange.call(this);
            }
        });

        var MultiPathDataNode = PathDataNode.extend({
            renderData: function () {
                var paths = this.srcElement.paths;

                if (paths.length > 0) {
                    var result = [],
                    i,
                    open;

                    for (i = 0; i < paths.length; i++) {
                        open = i < paths.length - 1;
                        result.push(printPath(paths[i], open));
                    }

                    return result.join(" ");
                }
            }
        });

        var MultiPathNode = PathNode.extend({
            createDataNode: function (srcElement) {
                return new MultiPathDataNode(srcElement);
            }
        });

        var CircleTransformNode = TransformNode.extend({
            transformOrigin: function () {
                var boundingBox = this.srcElement.geometry().bbox(),
                center = boundingBox.center(),
                originX = -center.x / boundingBox.width(),
                originY = -center.y / boundingBox.height();

                return originX + "," + originY;
            }
        });

        var CircleNode = ShapeNode.extend({
            createElement: function () {
                this.element = createElementVML("oval");
                this.setStyle();
            },

            createTransformNode: function (srcElement, transform) {
                return new CircleTransformNode(srcElement, transform);
            },

            geometryChange: function () {
                ShapeNode.fn.geometryChange.call(this);

                this.setStyle();
                this.refreshTransform();
            },

            mapStyle: function () {
                var geometry = this.srcElement.geometry();
                var radius = geometry.radius;
                var center = geometry.center;
                var diameter = radius * 2;

                var styles = ShapeNode.fn.mapStyle.call(this, diameter, diameter);
                styles.push(
                ["left", center.x - radius + "px"],
                ["top", center.y - radius + "px"]
            );

                return styles;
            }
        });

        var ArcDataNode = PathDataNode.extend({
            renderData: function () {
                return printPath(this.srcElement.toPath());
            }
        });

        var ArcNode = PathNode.extend({
            createDataNode: function (srcElement) {
                return new ArcDataNode(srcElement);
            }
        });

        var TextPathDataNode = PathDataNode.extend({
            createElement: function () {
                PathDataNode.fn.createElement.call(this);

                this.attr("textpathok", true);
            },

            renderData: function () {
                var rect = this.srcElement.rect();
                var center = rect.center();
                return "m " + printPoints([new g.Point(rect.topLeft().x, center.y)]) +
                   " l " + printPoints([new g.Point(rect.bottomRight().x, center.y)]);
            }
        });

        var TextPathNode = Node.extend({
            createElement: function () {
                this.element = createElementVML("textpath");

                this.attr("on", true);
                this.attr("fitpath", false);
                this.setStyle();
                this.setString();
            },

            optionsChange: function (e) {
                if (e.field === "content") {
                    this.setString();
                } else {
                    this.setStyle();
                }

                Node.fn.optionsChange.call(this, e);
            },

            mapStyle: function () {
                return [["font", this.srcElement.options.font]];
            },

            setString: function () {
                this.attr("string", this.srcElement.content());
            }
        });

        var TextNode = PathNode.extend({
            init: function (srcElement, transform, opacity) {
                this.path = new TextPathNode(srcElement);

                PathNode.fn.init.call(this, srcElement, transform, opacity);
            },

            createDataNode: function (srcElement) {
                return new TextPathDataNode(srcElement);
            },

            attachTo: function (domElement, pos) {
                this.path.attachTo(this.element);
                PathNode.fn.attachTo.call(this, domElement, pos);
            },

            optionsChange: function (e) {
                if (e.field === "font" || e.field === "content") {
                    this.path.optionsChange(e);
                    this.pathData.geometryChange(e);
                }

                PathNode.fn.optionsChange.call(this, e);
            }
        });

        var ImagePathDataNode = PathDataNode.extend({
            renderData: function () {
                var rect = this.srcElement.rect();
                var path = new d.Path().moveTo(rect.topLeft())
                                   .lineTo(rect.topRight())
                                   .lineTo(rect.bottomRight())
                                   .lineTo(rect.bottomLeft())
                                   .close();

                return printPath(path);
            }
        });

        var ImageFillNode = TransformNode.extend({
            init: function (srcElement, transform, opacity) {
                this.opacity = opacity;
                TransformNode.fn.init.call(this, srcElement, transform);
            },

            createElement: function () {
                this.element = createElementVML("fill");

                this.attr("type", "frame");
                this.attr("rotate", true);
                this.setOpacity();
                this.setSrc();
                this.setTransform();
            },

            optionsChange: function (e) {
                if (e.field === "src") {
                    this.setSrc();
                }

                TransformNode.fn.optionsChange.call(this, e);
            },

            geometryChange: function () {
                this.refresh();
            },

            refreshOpacity: function (opacity) {
                this.opacity = opacity;
                this.setOpacity();
            },

            setOpacity: function () {
                var attrs = [];
                this.mapOpacityTo(attrs, this.srcElement.options.opacity);
                this.allAttr(attrs);
            },

            setSrc: function () {
                this.attr("src", this.srcElement.src());
            },

            mapTransform: function () {
                var img = this.srcElement;
                var rawbbox = img.rawBBox();
                var rawcenter = rawbbox.center();

                var fillOrigin = COORDINATE_MULTIPLE / 2;
                var fillSize = COORDINATE_MULTIPLE;

                var x;
                var y;
                var width = rawbbox.width() / fillSize;
                var height = rawbbox.height() / fillSize;
                var angle = 0;

                var transform = this.transform;
                if (transform) {
                    var matrix = toMatrix(transform);
                    var sx = sqrt(matrix.a * matrix.a + matrix.b * matrix.b);
                    var sy = sqrt(matrix.c * matrix.c + matrix.d * matrix.d);

                    width *= sx;
                    height *= sy;

                    var ax = deg(atan2(matrix.b, matrix.d));
                    var ay = deg(atan2(-matrix.c, matrix.a));
                    angle = (ax + ay) / 2;

                    if (angle !== 0) {
                        var center = img.bbox().center();
                        x = (center.x - fillOrigin) / fillSize;
                        y = (center.y - fillOrigin) / fillSize;
                    } else {
                        x = (rawcenter.x * sx + matrix.e - fillOrigin) / fillSize;
                        y = (rawcenter.y * sy + matrix.f - fillOrigin) / fillSize;
                    }
                } else {
                    x = (rawcenter.x - fillOrigin) / fillSize;
                    y = (rawcenter.y - fillOrigin) / fillSize;
                }

                width = round(width, TRANSFORM_PRECISION);
                height = round(height, TRANSFORM_PRECISION);
                x = round(x, TRANSFORM_PRECISION);
                y = round(y, TRANSFORM_PRECISION);
                angle = round(angle, TRANSFORM_PRECISION);

                return [
                ["size", width + "," + height],
                ["position", x + "," + y],
                ["angle", angle]
            ];
            }
        });

        var ImageNode = PathNode.extend({
            createFillNode: function (srcElement, transform, opacity) {
                return new ImageFillNode(srcElement, transform, opacity);
            },

            createDataNode: function (srcElement) {
                return new ImagePathDataNode(srcElement);
            },

            optionsChange: function (e) {
                if (e.field === "src" || e.field === "transform") {
                    this.fill.optionsChange(e);
                }

                PathNode.fn.optionsChange.call(this, e);
            },

            geometryChange: function () {
                this.fill.geometryChange();
                PathNode.fn.geometryChange.call(this);
            },

            refreshTransform: function (transform) {
                PathNode.fn.refreshTransform.call(this, transform);
                this.fill.refresh(this.srcElement.currentTransform(transform));
            }
        });

        var nodeMap = {
            Group: GroupNode,
            Text: TextNode,
            Path: PathNode,
            MultiPath: MultiPathNode,
            Circle: CircleNode,
            Arc: ArcNode,
            Image: ImageNode
        };

        // Helper functions =======================================================
        function enableVML() {
            if (doc.namespaces && !doc.namespaces.kvml) {
                doc.namespaces.add("kvml", "urn:schemas-microsoft-com:vml");

                var stylesheet = doc.styleSheets.length > 30 ? doc.styleSheets[0] : doc.createStyleSheet();
                stylesheet.addRule(".kvml", "behavior:url(#default#VML)");
            }
        }

        function createElementVML(type) {
            var element = doc.createElement("kvml:" + type);
            element.className = "kvml";

            return element;
        }

        function printPoints(points) {
            var length = points.length;
            var result = [];

            for (var i = 0; i < length; i++) {
                result.push(points[i]
                .scaleCopy(COORDINATE_MULTIPLE)
                .toString(0, ",")
           );
            }

            return result.join(" ");
        }

        function printPath(path, open) {
            var segments = path.segments,
            length = segments.length;

            if (length > 0) {
                var parts = [],
                output,
                type,
                currentType,
                i;

                for (i = 1; i < length; i++) {
                    type = segmentType(segments[i - 1], segments[i]);
                    if (type !== currentType) {
                        currentType = type;
                        parts.push(type);
                    }

                    if (type === "l") {
                        parts.push(printPoints([segments[i].anchor()]));
                    } else {
                        parts.push(printPoints([
                        segments[i - 1].controlOut(),
                        segments[i].controlIn(),
                        segments[i].anchor()
                    ]));
                    }
                }

                output = "m " + printPoints([segments[0].anchor()]) + " " + parts.join(" ");
                if (path.options.closed) {
                    output += " x";
                }

                if (open !== true) {
                    output += " e";
                }

                return output;
            }
        }

        function segmentType(segmentStart, segmentEnd) {
            return segmentStart.controlOut() && segmentEnd.controlIn() ? "c" : "l";
        }

        function fillField(field) {
            return field.indexOf("fill") === 0 || field.indexOf(GRADIENT) === 0;
        }

        function stopColor(baseColor, stop) {
            var color;
            if (baseColor) {
                color = blendColors(baseColor, stop.color(), stop.opacity());
            } else {
                color = blendColors(stop.color(), "#fff", 1 - stop.opacity());
            }
            return color;
        }

        function blendColors(base, overlay, alpha) {
            var baseColor = new Color(base),
            overlayColor = new Color(overlay),
            r = blendChannel(baseColor.r, overlayColor.r, alpha),
            g = blendChannel(baseColor.g, overlayColor.g, alpha),
            b = blendChannel(baseColor.b, overlayColor.b, alpha);

            return new Color(r, g, b).toHex();
        }

        function blendChannel(a, b, alpha) {
            return math.round(alpha * b + (1 - alpha) * a);
        }

        // Exports ================================================================
        kendo.support.vml = (function () {
            var browser = kendo.support.browser;
            return browser.msie && browser.version < 9;
        })();


        var EMPTY_CLIP = "inherit";
        if (kendo.support.browser.msie && kendo.support.browser.version < 8) {
            EMPTY_CLIP = "rect(auto auto auto auto)";
        }

        if (kendo.support.vml) {
            d.SurfaceFactory.current.register("vml", Surface, 30);
        }

        deepExtend(d, {
            vml: {
                ArcDataNode: ArcDataNode,
                ArcNode: ArcNode,
                CircleTransformNode: CircleTransformNode,
                CircleNode: CircleNode,
                FillNode: FillNode,
                GroupNode: GroupNode,
                ImageNode: ImageNode,
                ImageFillNode: ImageFillNode,
                ImagePathDataNode: ImagePathDataNode,
                MultiPathDataNode: MultiPathDataNode,
                MultiPathNode: MultiPathNode,
                Node: Node,
                PathDataNode: PathDataNode,
                PathNode: PathNode,
                RootNode: RootNode,
                StrokeNode: StrokeNode,
                Surface: Surface,
                TextNode: TextNode,
                TextPathNode: TextPathNode,
                TextPathDataNode: TextPathDataNode,
                TransformNode: TransformNode
            }
        });

    })(window.kendo.jQuery);

    (function (global, parseFloat, undefined) {

        "use strict";

        // WARNING: removing the following jshint declaration and turning
        // == into === to make JSHint happy will break functionality.
        /* jshint eqnull:true */
        /* jshint loopfunc:true */
        /* jshint newcap:false */
        /* global VBArray */

        var kendo = global.kendo;

        // XXX: remove this junk (assume `true`) when we no longer have to support IE < 10
        var HAS_TYPED_ARRAYS = !!global.Uint8Array;

        var NL = "\n";

        var RESOURCE_COUNTER = 0;

        var BASE64 = (function () {
            var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
            return {
                decode: function (str) {
                    var input = str.replace(/[^A-Za-z0-9\+\/\=]/g, ""), i = 0, n = input.length, output = [];

                    while (i < n) {
                        var enc1 = keyStr.indexOf(input.charAt(i++));
                        var enc2 = keyStr.indexOf(input.charAt(i++));
                        var enc3 = keyStr.indexOf(input.charAt(i++));
                        var enc4 = keyStr.indexOf(input.charAt(i++));

                        var chr1 = (enc1 << 2) | (enc2 >>> 4);
                        var chr2 = ((enc2 & 15) << 4) | (enc3 >>> 2);
                        var chr3 = ((enc3 & 3) << 6) | enc4;

                        output.push(chr1);
                        if (enc3 != 64) {
                            output.push(chr2);
                        }
                        if (enc4 != 64) {
                            output.push(chr3);
                        }
                    }

                    return output;
                },
                encode: function (bytes) {
                    var i = 0, n = bytes.length;
                    var output = "";

                    while (i < n) {
                        var chr1 = bytes[i++];
                        var chr2 = bytes[i++];
                        var chr3 = bytes[i++];

                        var enc1 = chr1 >>> 2;
                        var enc2 = ((chr1 & 3) << 4) | (chr2 >>> 4);
                        var enc3 = ((chr2 & 15) << 2) | (chr3 >>> 6);
                        var enc4 = chr3 & 63;

                        if (i - n == 2) {
                            enc3 = enc4 = 64;
                        } else if (i - n == 1) {
                            enc4 = 64;
                        }

                        output += keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
                    }
                    return output;
                }
            };
        } ());

        var PAPER_SIZE = {
            a0: [2383.94, 3370.39],
            a1: [1683.78, 2383.94],
            a2: [1190.55, 1683.78],
            a3: [841.89, 1190.55],
            a4: [595.28, 841.89],
            a5: [419.53, 595.28],
            a6: [297.64, 419.53],
            a7: [209.76, 297.64],
            a8: [147.40, 209.76],
            a9: [104.88, 147.40],
            a10: [73.70, 104.88],
            b0: [2834.65, 4008.19],
            b1: [2004.09, 2834.65],
            b2: [1417.32, 2004.09],
            b3: [1000.63, 1417.32],
            b4: [708.66, 1000.63],
            b5: [498.90, 708.66],
            b6: [354.33, 498.90],
            b7: [249.45, 354.33],
            b8: [175.75, 249.45],
            b9: [124.72, 175.75],
            b10: [87.87, 124.72],
            c0: [2599.37, 3676.54],
            c1: [1836.85, 2599.37],
            c2: [1298.27, 1836.85],
            c3: [918.43, 1298.27],
            c4: [649.13, 918.43],
            c5: [459.21, 649.13],
            c6: [323.15, 459.21],
            c7: [229.61, 323.15],
            c8: [161.57, 229.61],
            c9: [113.39, 161.57],
            c10: [79.37, 113.39],
            executive: [521.86, 756.00],
            folio: [612.00, 936.00],
            legal: [612.00, 1008.00],
            letter: [612.00, 792.00],
            tabloid: [792.00, 1224.00]
        };

        function makeOutput() {
            var indentLevel = 0, output = BinaryStream();
            function out() {
                for (var i = 0; i < arguments.length; ++i) {
                    var x = arguments[i];
                    if (x === undefined) {
                        throw new Error("Cannot output undefined to PDF");
                    }
                    else if (x instanceof PDFValue) {
                        x.beforeRender(out);
                        x.render(out);
                    }
                    else if (isArray(x)) {
                        renderArray(x, out);
                    }
                    else if (isDate(x)) {
                        renderDate(x, out);
                    }
                    else if (typeof x == "number") {
                        if (isNaN(x)) {
                            throw new Error("Cannot output NaN to PDF");
                        }
                        // make sure it doesn't end up in exponent notation
                        var num = x.toFixed(7);
                        if (num.indexOf(".") >= 0) {
                            num = num.replace(/\.?0+$/, "");
                        }
                        if (num == "-0") {
                            num = "0";
                        }
                        output.writeString(num);
                    }
                    else if (/string|boolean/.test(typeof x)) {
                        output.writeString(x + "");
                    }
                    else if (typeof x.get == "function") {
                        output.write(x.get());
                    }
                    else if (typeof x == "object") {
                        if (!x) {
                            output.writeString("null");
                        } else {
                            out(new PDFDictionary(x));
                        }
                    }
                }
            }
            out.writeData = function (data) {
                output.write(data);
            };
            out.withIndent = function (f) {
                ++indentLevel;
                f(out);
                --indentLevel;
            };
            out.indent = function () {
                out(NL, pad("", indentLevel * 2, "  "));
                out.apply(null, arguments);
            };
            out.offset = function () {
                return output.offset();
            };
            out.toString = function () {
                throw new Error("FIX CALLER");
            };
            out.get = function () {
                return output.get();
            };
            out.stream = function () {
                return output;
            };
            return out;
        }

        function wrapObject(value, id) {
            var beforeRender = value.beforeRender;
            var renderValue = value.render;

            value.beforeRender = function () { };

            value.render = function (out) {
                out(id, " 0 R");
            };

            value.renderFull = function (out) {
                value._offset = out.offset();
                out(id, " 0 obj ");
                beforeRender.call(value, out);
                renderValue.call(value, out);
                out(" endobj");
            };
        }

        function PDFDocument(options) {
            var self = this;
            var out = makeOutput();
            var objcount = 0;
            var objects = [];

            function getOption(name, defval) {
                return (options && options[name] != null) ? options[name] : defval;
            }

            self.getOption = getOption;

            self.attach = function (value) {
                if (objects.indexOf(value) < 0) {
                    wrapObject(value, ++objcount);
                    objects.push(value);
                }
                return value;
            };

            self.FONTS = {};
            self.IMAGES = {};

            var paperSize = getOption("paperSize", PAPER_SIZE.a4);
            if (typeof paperSize == "string") {
                paperSize = PAPER_SIZE[paperSize.toLowerCase()];
                if (paperSize == null) {
                    throw new Error("Unknown paper size");
                }
            }

            paperSize[0] = unitsToPoints(paperSize[0]);
            paperSize[1] = unitsToPoints(paperSize[1]);

            if (getOption("landscape", false)) {
                paperSize = [
                Math.max(paperSize[0], paperSize[1]),
                Math.min(paperSize[0], paperSize[1])
            ];
            }

            var margin = getOption("margin");
            if (margin) {
                margin.left = unitsToPoints(margin.left, 0);
                margin.top = unitsToPoints(margin.top, 0);
                margin.right = unitsToPoints(margin.right, 0);
                margin.bottom = unitsToPoints(margin.bottom, 0);
                if (getOption("addMargin")) {
                    paperSize[0] += margin.left + margin.right;
                    paperSize[1] += margin.top + margin.bottom;
                }
            }

            var contentWidth = paperSize[0];
            var contentHeight = paperSize[1];

            if (margin) {
                contentWidth -= margin.left + margin.right;
                contentHeight -= margin.top + margin.bottom;
            }

            var catalog = self.attach(new PDFCatalog());
            var pageTree = self.attach(new PDFPageTree([0, 0, paperSize[0], paperSize[1]]));
            catalog.setPages(pageTree);

            self.addPage = function () {
                var content = new PDFStream(makeOutput(), null, true);
                var page = new PDFPage(self, {
                    Contents: self.attach(content),
                    Parent: pageTree
                });
                page._content = content;
                pageTree.addPage(self.attach(page));

                // canvas-like coord. system.  (0,0) is upper-left.
                // text must be vertically mirorred before drawing.
                page.transform(1, 0, 0, -1, 0, paperSize[1]);

                if (margin) {
                    page.translate(margin.left, margin.top);
                    // XXX: clip to right/bottom margin.  Make this optional?
                    page.rect(0, 0, contentWidth, contentHeight);
                    page.clip();
                }

                return page;
            };

            self.render = function () {
                var i;
                /// file header
                out("%PDF-1.4", NL, "%\xc2\xc1\xda\xcf\xce", NL, NL);

                /// file body
                for (i = 0; i < objects.length; ++i) {
                    objects[i].renderFull(out);
                    out(NL, NL);
                }

                /// cross-reference table
                var xrefOffset = out.offset();
                out("xref", NL, 0, " ", objects.length + 1, NL);
                out("0000000000 65535 f ", NL);
                for (i = 0; i < objects.length; ++i) {
                    out(zeropad(objects[i]._offset, 10), " 00000 n ", NL);
                }
                out(NL);

                /// trailer
                out("trailer", NL);
                out(new PDFDictionary({
                    Size: objects.length + 1,
                    Root: catalog,
                    Info: new PDFDictionary({
                        Producer: new PDFString("Kendo UI PDF Generator"),
                        Title: new PDFString(getOption("title", "")),
                        Author: new PDFString(getOption("author", "")),
                        Subject: new PDFString(getOption("subject", "")),
                        Keywords: new PDFString(getOption("keywords", "")),
                        Creator: new PDFString(getOption("creator", "Kendo UI PDF Generator")),
                        CreationDate: getOption("date", new Date())
                    })
                }), NL, NL);

                /// end
                out("startxref", NL, xrefOffset, NL);
                out("%%EOF", NL);

                return out.stream().offset(0);
            };
        }

        var FONT_CACHE = {
            "Times-Roman": true,
            "Times-Bold": true,
            "Times-Italic": true,
            "Times-BoldItalic": true,
            "Helvetica": true,
            "Helvetica-Bold": true,
            "Helvetica-Oblique": true,
            "Helvetica-BoldOblique": true,
            "Courier": true,
            "Courier-Bold": true,
            "Courier-Oblique": true,
            "Courier-BoldOblique": true,
            "Symbol": true,
            "ZapfDingbats": true
        };

        function loadBinary(url, cont) {
            function error() {
                if (global.console) {
                    if (global.console.error) {
                        global.console.error("Cannot load URL: %s", url);
                    } else {
                        global.console.log("Cannot load URL: %s", url);
                    }
                }
                cont(null);
            }
            var req = new XMLHttpRequest();
            req.open('GET', url, true);
            if (HAS_TYPED_ARRAYS) {
                req.responseType = "arraybuffer";
            }
            req.onload = function () {
                if (req.status == 200 || req.status == 304) {
                    if (HAS_TYPED_ARRAYS) {
                        cont(new Uint8Array(req.response));
                    } else {
                        cont(new VBArray(req.responseBody).toArray()); // IE9 only
                    }
                } else {
                    error();
                }
            };
            req.onerror = error;
            req.send(null);
        }

        function loadFont(url, cont) {
            var font = FONT_CACHE[url];
            if (font) {
                cont(font);
            } else {
                loadBinary(url, function (data) {
                    if (data == null) {
                        throw new Error("Cannot load font from " + url);
                    } else {
                        var font = new kendo.pdf.TTFFont(data);
                        FONT_CACHE[url] = font;
                        cont(font);
                    }
                });
            }
        }

        var IMAGE_CACHE = {};

        function loadImage(url, cont) {
            var img = IMAGE_CACHE[url];
            if (img) {
                cont(img);
            } else {
                img = new Image();
                img.onload = function () {
                    var canvas = document.createElement("canvas");
                    canvas.width = img.width;
                    canvas.height = img.height;
                    var ctx = canvas.getContext("2d");

                    ctx.drawImage(img, 0, 0);

                    var imgdata;
                    try {
                        imgdata = ctx.getImageData(0, 0, img.width, img.height);
                    } catch (ex) {
                        // it tainted the canvas -- can't draw it.
                        return cont(IMAGE_CACHE[url] = "TAINTED");
                    }

                    // in case it contains transparency, we must separate rgb data from the alpha
                    // channel and create a PDFRawImage image with opacity.  otherwise we can use a
                    // PDFJpegImage.
                    //
                    // to do this in one step, we create the rgb and alpha streams anyway, even if
                    // we might end up not using them if hasAlpha remains false.

                    var hasAlpha = false, rgb = BinaryStream(), alpha = BinaryStream();
                    var rawbytes = imgdata.data;
                    var i = 0;
                    while (i < rawbytes.length) {
                        rgb.writeByte(rawbytes[i++]);
                        rgb.writeByte(rawbytes[i++]);
                        rgb.writeByte(rawbytes[i++]);
                        var a = rawbytes[i++];
                        if (a < 255) {
                            hasAlpha = true;
                        }
                        alpha.writeByte(a);
                    }

                    if (hasAlpha) {
                        img = new PDFRawImage(img.width, img.height, rgb, alpha);
                    } else {
                        // no transparency, encode as JPEG.
                        var data = canvas.toDataURL("image/jpeg");
                        data = data.substr(data.indexOf(";base64,") + 8);

                        var stream = BinaryStream();
                        stream.writeBase64(data);
                        stream.offset(0);
                        img = new PDFJpegImage(img.width, img.height, stream);
                    }

                    cont(IMAGE_CACHE[url] = img);
                };

                img.onerror = function () {
                    cont(IMAGE_CACHE[url] = "TAINTED");
                };

                img.crossOrigin = "Anonymous";
                img.src = url;

                // hack from https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
                // make sure the load event fires for cached images too
                if (img.complete || img.complete === undefined) {
                    img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
                    img.src = url;
                }
            }
        }

        function manyLoader(loadOne) {
            return function (urls, callback) {
                var n = urls.length, i = n;
                if (n === 0) {
                    return callback();
                }
                while (i-- > 0) {
                    loadOne(urls[i], function () {
                        if (--n === 0) {
                            callback();
                        }
                    });
                }
            };
        }

        var loadFonts = manyLoader(loadFont);
        var loadImages = manyLoader(loadImage);

        PDFDocument.prototype = {
            loadFonts: loadFonts,
            loadImages: loadImages,

            getFont: function (url) {
                var font = this.FONTS[url];
                if (!font) {
                    font = FONT_CACHE[url];
                    if (!font) {
                        throw new Error("Font " + url + " has not been loaded");
                    }
                    if (font === true) {
                        font = this.attach(new PDFStandardFont(url));
                    } else {
                        font = this.attach(new PDFFont(this, font));
                    }
                    this.FONTS[url] = font;
                }
                return font;
            },

            getImage: function (url) {
                var img = this.IMAGES[url];
                if (!img) {
                    img = IMAGE_CACHE[url];
                    if (!img) {
                        throw new Error("Image " + url + " has not been loaded");
                    }
                    if (img === "TAINTED") {
                        return null;
                    }
                    img = this.IMAGES[url] = this.attach(img.asStream(this));
                }
                return img;
            },

            getOpacityGS: function (opacity, forStroke) {
                var id = parseFloat(opacity).toFixed(3);
                opacity = parseFloat(id);
                id += forStroke ? "S" : "F";
                var cache = this._opacityGSCache || (this._opacityGSCache = {});
                var gs = cache[id];
                if (!gs) {
                    var props = {
                        Type: _("ExtGState")
                    };
                    if (forStroke) {
                        props.CA = opacity;
                    } else {
                        props.ca = opacity;
                    }
                    gs = this.attach(new PDFDictionary(props));
                    gs._resourceName = _("GS" + (++RESOURCE_COUNTER));
                    cache[id] = gs;
                }
                return gs;
            },

            dict: function (props) {
                return new PDFDictionary(props);
            },

            name: function (str) {
                return _(str);
            },

            stream: function (props, content) {
                return new PDFStream(content, props);
            }
        };

        /* -----[ utils ]----- */

        function pad(str, len, ch) {
            while (str.length < len) {
                str = ch + str;
            }
            return str;
        }

        function zeropad(n, len) {
            return pad(n + "", len, "0");
        }

        function hasOwnProperty(obj, key) {
            return Object.prototype.hasOwnProperty.call(obj, key);
        }

        var isArray = Array.isArray || function (obj) {
            return obj instanceof Array;
        };

        function isDate(obj) {
            return obj instanceof Date;
        }

        function renderArray(a, out) {
            out("[");
            if (a.length > 0) {
                out.withIndent(function () {
                    for (var i = 0; i < a.length; ++i) {
                        if (i > 0 && i % 8 === 0) {
                            out.indent(a[i]);
                        } else {
                            out(" ", a[i]);
                        }
                    }
                });
                //out.indent();
            }
            out(" ]");
        }

        function renderDate(date, out) {
            out("(D:",
            zeropad(date.getUTCFullYear(), 4),
            zeropad(date.getUTCMonth() + 1, 2),
            zeropad(date.getUTCDate(), 2),
            zeropad(date.getUTCHours(), 2),
            zeropad(date.getUTCMinutes(), 2),
            zeropad(date.getUTCSeconds(), 2),
            "Z)");
        }

        function mm2pt(mm) {
            return mm * (72 / 25.4);
        }

        function cm2pt(cm) {
            return mm2pt(cm * 10);
        }

        function in2pt(inch) {
            return inch * 72;
        }

        function unitsToPoints(x, def) {
            if (typeof x == "number") {
                return x;
            }
            if (typeof x == "string") {
                var m;
                m = /^\s*([0-9.]+)\s*(mm|cm|in|pt)\s*$/.exec(x);
                if (m) {
                    var num = parseFloat(m[1]);
                    if (!isNaN(num)) {
                        if (m[2] == "pt") {
                            return num;
                        }
                        return {
                            "mm": mm2pt,
                            "cm": cm2pt,
                            "in": in2pt
                        }[m[2]](num);
                    }
                }
            }
            if (def != null) {
                return def;
            }
            throw new Error("Can't parse unit: " + x);
        }

        /* -----[ PDF basic objects ]----- */

        function PDFValue() { }

        PDFValue.prototype.beforeRender = function () { };

        function defclass(Ctor, proto, Base) {
            if (!Base) {
                Base = PDFValue;
            }
            Ctor.prototype = new Base();
            for (var i in proto) {
                if (hasOwnProperty(proto, i)) {
                    Ctor.prototype[i] = proto[i];
                }
            }
            return Ctor;
        }

        /// strings

        var PDFString = defclass(function PDFString(value) {
            this.value = value;
        }, {
            render: function (out) {
                //out("(\xFE\xFF", utf16_be_encode(this.escape()), ")");
                var txt = "", esc = this.escape();
                for (var i = 0; i < esc.length; ++i) {
                    txt += String.fromCharCode(esc.charCodeAt(i) & 0xFF);
                }
                out("(", txt, ")");
            },
            escape: function () {
                return this.value.replace(/([\(\)\\])/g, "\\$1");
            },
            toString: function () {
                return this.value;
            }
        });

        var PDFHexString = defclass(function PDFHexString(value) {
            this.value = value;
        }, {
            render: function (out) {
                out("<");
                for (var i = 0; i < this.value.length; ++i) {
                    out(zeropad(this.value.charCodeAt(i).toString(16), 4));
                }
                out(">");
            }
        }, PDFString);

        /// names

        var PDFName = defclass(function PDFName(name) {
            this.name = name;
        }, {
            render: function (out) {
                out("/" + this.escape());
            },
            escape: function () {
                return this.name.replace(/[^\x21-\x7E]/g, function (c) {
                    return "#" + zeropad(c.charCodeAt(0).toString(16), 2);
                });
            },
            toString: function () {
                return this.name;
            }
        });

        var PDFName_cache = {};
        PDFName.get = _;

        function _(name) {
            if (hasOwnProperty(PDFName_cache, name)) {
                return PDFName_cache[name];
            }
            return (PDFName_cache[name] = new PDFName(name));
        }

        /// dictionary

        var PDFDictionary = defclass(function PDFDictionary(props) {
            this.props = props;
        }, {
            render: function (out) {
                var props = this.props, empty = true;
                out("<<");
                out.withIndent(function () {
                    for (var i in props) {
                        if (hasOwnProperty(props, i) && !/^_/.test(i)) {
                            empty = false;
                            out.indent(_(i), " ", props[i]);
                        }
                    }
                });
                if (!empty) {
                    out.indent();
                }
                out(">>");
            }
        });

        /// streams

        var PDFStream = defclass(function PDFStream(data, props, compress) {
            if (typeof data == "string") {
                var tmp = BinaryStream();
                tmp.write(data);
                data = tmp;
            }
            this.data = data;
            this.props = props || {};
            this.compress = compress;
        }, {
            render: function (out) {
                var data = this.data.get(), props = this.props;
                if (this.compress && global.pako && typeof global.pako.deflate == "function") {
                    if (!props.Filter) {
                        props.Filter = [];
                    } else if (!(props.Filter instanceof Array)) {
                        props.Filter = [props.Filter];
                    }
                    props.Filter.unshift(_("FlateDecode"));
                    data = global.pako.deflate(data);
                }
                props.Length = data.length;
                out(new PDFDictionary(props), " stream", NL);
                out.writeData(data);
                out(NL, "endstream");
            }
        });

        /// catalog

        var PDFCatalog = defclass(function PDFCatalog(props) {
            props = this.props = props || {};
            props.Type = _("Catalog");
        }, {
            setPages: function (pagesObj) {
                this.props.Pages = pagesObj;
            }
        }, PDFDictionary);

        /// page tree

        var PDFPageTree = defclass(function PDFPageTree(mediabox) {
            this.props = {
                Type: _("Pages"),
                Kids: [],
                Count: 0,

                MediaBox: mediabox
            };
        }, {
            addPage: function (pageObj) {
                this.props.Kids.push(pageObj);
                this.props.Count++;
            }
        }, PDFDictionary);

        /// images

        // JPEG

        function PDFJpegImage(width, height, data) {
            this.asStream = function () {
                var stream = new PDFStream(data, {
                    Type: _("XObject"),
                    Subtype: _("Image"),
                    Width: width,
                    Height: height,
                    BitsPerComponent: 8,
                    ColorSpace: _("DeviceRGB"),
                    Filter: _("DCTDecode")
                });
                stream._resourceName = _("I" + (++RESOURCE_COUNTER));
                return stream;
            };
        }

        // PDFRawImage will be used for images with transparency (PNG)

        function PDFRawImage(width, height, rgb, alpha) {
            this.asStream = function (pdf) {
                var mask = new PDFStream(alpha, {
                    Type: _("XObject"),
                    Subtype: _("Image"),
                    Width: width,
                    Height: height,
                    BitsPerComponent: 8,
                    ColorSpace: _("DeviceGray")
                }, true);
                var stream = new PDFStream(rgb, {
                    Type: _("XObject"),
                    Subtype: _("Image"),
                    Width: width,
                    Height: height,
                    BitsPerComponent: 8,
                    ColorSpace: _("DeviceRGB"),
                    SMask: pdf.attach(mask)
                }, true);
                stream._resourceName = _("I" + (++RESOURCE_COUNTER));
                return stream;
            };
        }

        /// standard fonts

        var PDFStandardFont = defclass(function PDFStandardFont(name) {
            this.props = {
                Type: _("Font"),
                Subtype: _("Type1"),
                BaseFont: _(name)
            };
            this._resourceName = _("F" + (++RESOURCE_COUNTER));
        }, {
            encodeText: function (str) {
                return new PDFString(str + "");
            }
        }, PDFDictionary);

        /// TTF fonts

        var PDFFont = defclass(function PDFFont(pdf, font, props) {
            props = this.props = props || {};
            props.Type = _("Font");
            props.Subtype = _("Type0");
            props.Encoding = _("Identity-H");

            this._pdf = pdf;
            this._font = font;
            this._sub = font.makeSubset();
            this._resourceName = _("F" + (++RESOURCE_COUNTER));

            var head = font.head;

            this.name = font.psName;
            var scale = this.scale = font.scale;
            this.bbox = [
            head.xMin * scale,
            head.yMin * scale,
            head.xMax * scale,
            head.yMax * scale
        ];

            this.italicAngle = font.post.italicAngle;
            this.ascent = font.ascent * scale;
            this.descent = font.descent * scale;
            this.lineGap = font.lineGap * scale;
            this.capHeight = font.os2.capHeight || this.ascent;
            this.xHeight = font.os2.xHeight || 0;
            this.stemV = 0;

            this.familyClass = (font.os2.familyClass || 0) >> 8;
            this.isSerif = this.familyClass >= 1 && this.familyClass <= 7;
            this.isScript = this.familyClass == 10;

            this.flags = ((font.post.isFixedPitch ? 1 : 0) |
                      (this.isSerif ? 1 << 1 : 0) |
                      (this.isScript ? 1 << 3 : 0) |
                      (this.italicAngle !== 0 ? 1 << 6 : 0) |
                      (1 << 5));
        }, {
            encodeText: function (text) {
                return new PDFHexString(this._sub.encodeText(text + ""));
            },
            beforeRender: function () {
                var self = this;
                var font = self._font;
                var sub = self._sub;

                // write the TTF data
                var data = sub.render();
                var fontStream = new PDFStream(BinaryStream(data), {
                    Length1: data.length
                }, true);

                var descriptor = self._pdf.attach(new PDFDictionary({
                    Type: _("FontDescriptor"),
                    FontName: _(self._sub.psName),
                    FontBBox: self.bbox,
                    Flags: self.flags,
                    StemV: self.stemV,
                    ItalicAngle: self.italicAngle,
                    Ascent: self.ascent,
                    Descent: self.descent,
                    CapHeight: self.capHeight,
                    XHeight: self.xHeight,
                    FontFile2: self._pdf.attach(fontStream)
                }));

                var cmap = sub.ncid2ogid;
                var firstChar = sub.firstChar;
                var lastChar = sub.lastChar;
                var charWidths = [];
                (function loop(i, chunk) {
                    if (i <= lastChar) {
                        var gid = cmap[i];
                        if (gid == null) {
                            loop(i + 1);
                        } else {
                            if (!chunk) {
                                charWidths.push(i, chunk = []);
                            }
                            chunk.push(self._font.widthOfGlyph(gid));
                            loop(i + 1, chunk);
                        }
                    }
                })(firstChar);

                // As if two dictionaries weren't enough, we need another
                // one, the "descendant font".  Only that one can be of
                // Subtype CIDFontType2.  PDF is the X11 of document
                // formats: portable but full of legacy that nobody cares
                // about anymore.

                var descendant = new PDFDictionary({
                    Type: _("Font"),
                    Subtype: _("CIDFontType2"),
                    BaseFont: _(self._sub.psName),
                    CIDSystemInfo: new PDFDictionary({
                        Registry: new PDFString("Adobe"),
                        Ordering: new PDFString("Identity"),
                        Supplement: 0
                    }),
                    FontDescriptor: descriptor,
                    FirstChar: firstChar,
                    LastChar: lastChar,
                    DW: Math.round(self._font.widthOfGlyph(0)),
                    W: charWidths,
                    CIDToGIDMap: self._pdf.attach(self._makeCidToGidMap())
                });

                var dict = self.props;
                dict.BaseFont = _(self._sub.psName);
                dict.DescendantFonts = [self._pdf.attach(descendant)];

                // Compute the ToUnicode map so that apps can extract
                // meaningful text from the PDF.
                var unimap = new PDFToUnicodeCmap(firstChar, lastChar, sub.subset);
                var unimapStream = new PDFStream(makeOutput(), null, true);
                unimapStream.data(unimap);
                dict.ToUnicode = self._pdf.attach(unimapStream);
            },
            _makeCidToGidMap: function () {
                return new PDFStream(BinaryStream(this._sub.cidToGidMap()), null, true);
            }
        }, PDFDictionary);

        var PDFToUnicodeCmap = defclass(function PDFUnicodeCMap(firstChar, lastChar, map) {
            this.firstChar = firstChar;
            this.lastChar = lastChar;
            this.map = map;
        }, {
            render: function (out) {
                out.indent("/CIDInit /ProcSet findresource begin");
                out.indent("12 dict begin");
                out.indent("begincmap");
                out.indent("/CIDSystemInfo <<");
                out.indent("  /Registry (Adobe)");
                out.indent("  /Ordering (UCS)");
                out.indent("  /Supplement 0");
                out.indent(">> def");
                out.indent("/CMapName /Adobe-Identity-UCS def");
                out.indent("/CMapType 2 def");
                out.indent("1 begincodespacerange");
                out.indent("  <0000><ffff>");
                out.indent("endcodespacerange");

                var self = this;
                out.indent(self.lastChar - self.firstChar + 1, " beginbfchar");
                out.withIndent(function () {
                    for (var code = self.firstChar; code <= self.lastChar; ++code) {
                        var unicode = self.map[code];
                        out.indent("<", zeropad(code.toString(16), 4), ">",
                               "<", zeropad(unicode.toString(16), 4), ">");
                    }
                });
                out.indent("endbfchar");

                out.indent("endcmap");
                out.indent("CMapName currentdict /CMap defineresource pop");
                out.indent("end");
                out.indent("end");
            }
        });

        /// gradients

        function makeGradientFunctions(stops) {
            var hasAlpha = false;
            var opacities = [];
            var colors = [];
            var offsets = [];
            var encode = [];
            for (var i = 1; i < stops.length; ++i) {
                var prev = stops[i - 1];
                var cur = stops[i];
                var prevColor = prev.color;
                var curColor = cur.color;
                colors.push({
                    FunctionType: 2,
                    Domain: [0, 1],
                    Range: [0, 1, 0, 1, 0, 1],
                    N: 1,
                    C0: [prevColor.r, prevColor.g, prevColor.b],
                    C1: [curColor.r, curColor.g, curColor.b]
                });
                opacities.push({
                    FunctionType: 2,
                    Domain: [0, 1],
                    Range: [0, 1],
                    N: 1,
                    C0: [prevColor.a],
                    C1: [curColor.a]
                });
                if (prevColor.a < 1 || curColor.a < 1) {
                    hasAlpha = true;
                }
                offsets.push(cur.offset);
                encode.push(0, 1);
            }
            offsets.pop();
            return {
                hasAlpha: hasAlpha,
                colors: assemble(colors),
                opacities: hasAlpha ? assemble(opacities) : null
            };
            function assemble(funcs) {
                if (funcs.length == 1) {
                    return funcs[0];
                }
                return {
                    FunctionType: 3,
                    Functions: funcs,
                    Domain: [0, 1],
                    Bounds: offsets,
                    Encode: encode
                };
            }
        }

        function makeGradient(pdf, gradient, box) {
            var isRadial = gradient.type == "radial";
            var funcs = makeGradientFunctions(gradient.stops);
            var coords = isRadial ? [
            gradient.start.x, gradient.start.y, gradient.start.r,
            gradient.end.x, gradient.end.y, gradient.end.r
        ] : [
            gradient.start.x, gradient.start.y,
            gradient.end.x, gradient.end.y
        ];
            var shading = {
                Type: _("Shading"),
                ShadingType: isRadial ? 3 : 2,
                ColorSpace: _("DeviceRGB"),
                Coords: coords,
                Domain: [0, 1],
                Function: funcs.colors,
                Extend: [true, true]
            };
            var opacity = funcs.hasAlpha ? {
                Type: _("ExtGState"),
                AIS: false,
                CA: 1,
                ca: 1,
                SMask: {
                    Type: _("Mask"),
                    S: _("Luminosity"),
                    G: pdf.attach(new PDFStream("/a0 gs /s0 sh", {
                        Type: _("XObject"),
                        Subtype: _("Form"),
                        FormType: 1,
                        BBox: (gradient.userSpace ? [
                        box.left, box.top + box.height, box.left + box.width, box.top
                    ] : [0, 1, 1, 0]),
                        Group: {
                            Type: _("Group"),
                            S: _("Transparency"),
                            CS: _("DeviceGray"),
                            I: true
                        },
                        Resources: {
                            ExtGState: {
                                a0: { CA: 1, ca: 1 }
                            },
                            Shading: {
                                s0: {
                                    ColorSpace: _("DeviceGray"),
                                    Coords: coords,
                                    Domain: [0, 1],
                                    ShadingType: isRadial ? 3 : 2,
                                    Function: funcs.opacities,
                                    Extend: [true, true]
                                }
                            }
                        }
                    }))
                }
            } : null;
            return {
                hasAlpha: funcs.hasAlpha,
                shading: new PDFDictionary(shading),
                opacity: new PDFDictionary(opacity)
            };
        }

        /// page object

        var PDFPage = defclass(function PDFPage(pdf, props) {
            this._pdf = pdf;
            this._rcount = 0;
            this._textMode = false;
            this._fontResources = {};
            this._gsResources = {};
            this._xResources = {};
            this._patResources = {};
            this._shResources = {};
            this._opacity = 1;
            this._matrix = [1, 0, 0, 1, 0, 0];

            this._font = null;
            this._fontSize = null;

            this._contextStack = [];

            props = this.props = props || {};
            props.Type = _("Page");
            props.ProcSet = [
            _("PDF"),
            _("Text"),
            _("ImageB"),
            _("ImageC"),
            _("ImageI")
        ];
            props.Resources = new PDFDictionary({
                Font: new PDFDictionary(this._fontResources),
                ExtGState: new PDFDictionary(this._gsResources),
                XObject: new PDFDictionary(this._xResources),
                Pattern: new PDFDictionary(this._patResources),
                Shading: new PDFDictionary(this._shResources)
            });
        }, {
            _out: function () {
                this._content.data.apply(null, arguments);
            },
            transform: function (a, b, c, d, e, f) {
                if (!isIdentityMatrix(arguments)) {
                    this._matrix = mmul(this._matrix, arguments);
                    this._out(a, " ", b, " ", c, " ", d, " ", e, " ", f, " cm");
                    // XXX: debug
                    // this._out(" % current matrix: ", this._matrix);
                    this._out(NL);
                }
            },
            translate: function (dx, dy) {
                this.transform(1, 0, 0, 1, dx, dy);
            },
            scale: function (sx, sy) {
                this.transform(sx, 0, 0, sy, 0, 0);
            },
            rotate: function (angle) {
                var cos = Math.cos(angle), sin = Math.sin(angle);
                this.transform(cos, sin, -sin, cos, 0, 0);
            },
            beginText: function () {
                this._textMode = true;
                this._out("BT", NL);
            },
            endText: function () {
                this._textMode = false;
                this._out("ET", NL);
            },
            _requireTextMode: function () {
                if (!this._textMode) {
                    throw new Error("Text mode required; call page.beginText() first");
                }
            },
            _requireFont: function () {
                if (!this._font) {
                    throw new Error("No font selected; call page.setFont() first");
                }
            },
            setFont: function (font, size) {
                this._requireTextMode();
                if (font == null) {
                    font = this._font;
                } else if (!(font instanceof PDFFont)) {
                    font = this._pdf.getFont(font);
                }
                if (size == null) {
                    size = this._fontSize;
                }
                this._fontResources[font._resourceName] = font;
                this._font = font;
                this._fontSize = size;
                this._out(font._resourceName, " ", size, " Tf", NL);
            },
            setTextLeading: function (size) {
                this._requireTextMode();
                this._out(size, " TL", NL);
            },
            setTextRenderingMode: function (mode) {
                this._requireTextMode();
                this._out(mode, " Tr", NL);
            },
            showText: function (text) {
                this._requireFont();
                this._out(this._font.encodeText(text), " Tj", NL);
            },
            showTextNL: function (text) {
                this._requireFont();
                this._out(this._font.encodeText(text), " '", NL);
            },
            setStrokeColor: function (r, g, b) {
                this._out(r, " ", g, " ", b, " RG", NL);
            },
            setOpacity: function (opacity) {
                this.setFillOpacity(opacity);
                this.setStrokeOpacity(opacity);
                this._opacity *= opacity;
            },
            setStrokeOpacity: function (opacity) {
                if (opacity < 1) {
                    var gs = this._pdf.getOpacityGS(this._opacity * opacity, true);
                    this._gsResources[gs._resourceName] = gs;
                    this._out(gs._resourceName, " gs", NL);
                }
            },
            setFillColor: function (r, g, b) {
                this._out(r, " ", g, " ", b, " rg", NL);
            },
            setFillOpacity: function (opacity) {
                if (opacity < 1) {
                    var gs = this._pdf.getOpacityGS(this._opacity * opacity, false);
                    this._gsResources[gs._resourceName] = gs;
                    this._out(gs._resourceName, " gs", NL);
                }
            },
            gradient: function (gradient, box) {
                this.save();
                this.rect(box.left, box.top, box.width, box.height);
                this.clip();
                if (!gradient.userSpace) {
                    this.transform(box.width, 0, 0, box.height, box.left, box.top);
                }
                var g = makeGradient(this._pdf, gradient, box);
                var sname, oname;
                sname = "S" + (++RESOURCE_COUNTER);
                this._shResources[sname] = this._pdf.attach(g.shading);
                if (g.hasAlpha) {
                    oname = "O" + (++RESOURCE_COUNTER);
                    this._gsResources[oname] = this._pdf.attach(g.opacity);
                    this._out("/" + oname + " gs ");
                }
                this._out("/" + sname + " sh", NL);
                this.restore();
            },
            setDashPattern: function (dashArray, dashPhase) {
                this._out(dashArray, " ", dashPhase, " d", NL);
            },
            setLineWidth: function (width) {
                this._out(width, " w", NL);
            },
            setLineCap: function (lineCap) {
                this._out(lineCap, " J", NL);
            },
            setLineJoin: function (lineJoin) {
                this._out(lineJoin, " j", NL);
            },
            setMitterLimit: function (mitterLimit) {
                this._out(mitterLimit, " M", NL);
            },
            save: function () {
                this._contextStack.push(this._context());
                this._out("q", NL);
            },
            restore: function () {
                this._out("Q", NL);
                this._context(this._contextStack.pop());
            },

            // paths
            moveTo: function (x, y) {
                this._out(x, " ", y, " m", NL);
            },
            lineTo: function (x, y) {
                this._out(x, " ", y, " l", NL);
            },
            bezier: function (x1, y1, x2, y2, x3, y3) {
                this._out(x1, " ", y1, " ", x2, " ", y2, " ", x3, " ", y3, " c", NL);
            },
            bezier1: function (x1, y1, x3, y3) {
                this._out(x1, " ", y1, " ", x3, " ", y3, " y", NL);
            },
            bezier2: function (x2, y2, x3, y3) {
                this._out(x2, " ", y2, " ", x3, " ", y3, " v", NL);
            },
            close: function () {
                this._out("h", NL);
            },
            rect: function (x, y, w, h) {
                this._out(x, " ", y, " ", w, " ", h, " re", NL);
            },
            ellipse: function (x, y, rx, ry) {
                function _X(v) { return x + v; }
                function _Y(v) { return y + v; }

                // how to get to the "magic number" is explained here:
                // http://www.whizkidtech.redprince.net/bezier/circle/kappa/
                var k = 0.5522847498307936;

                this.moveTo(_X(0), _Y(ry));
                this.bezier(
                _X(rx * k), _Y(ry),
                _X(rx), _Y(ry * k),
                _X(rx), _Y(0)
            );
                this.bezier(
                _X(rx), _Y(-ry * k),
                _X(rx * k), _Y(-ry),
                _X(0), _Y(-ry)
            );
                this.bezier(
                _X(-rx * k), _Y(-ry),
                _X(-rx), _Y(-ry * k),
                _X(-rx), _Y(0)
            );
                this.bezier(
                _X(-rx), _Y(ry * k),
                _X(-rx * k), _Y(ry),
                _X(0), _Y(ry)
            );
            },
            circle: function (x, y, r) {
                this.ellipse(x, y, r, r);
            },
            stroke: function () {
                this._out("S", NL);
            },
            nop: function () {
                this._out("n", NL);
            },
            clip: function () {
                this._out("W n", NL);
            },
            closeStroke: function () {
                this._out("s", NL);
            },
            fill: function () {
                this._out("f", NL);
            },
            fillStroke: function () {
                this._out("B", NL);
            },
            drawImage: function (url) {
                var img = this._pdf.getImage(url);
                if (img) { // the result can be null for a cross-domain image
                    this._xResources[img._resourceName] = img;
                    this._out(img._resourceName, " Do", NL);
                }
            },
            comment: function (txt) {
                var self = this;
                txt.split(/\r?\n/g).forEach(function (line) {
                    self._out("% ", line, NL);
                });
            },

            // internal
            _context: function (val) {
                if (val != null) {
                    this._opacity = val.opacity;
                    this._matrix = val.matrix;
                } else {
                    return {
                        opacity: this._opacity,
                        matrix: this._matrix
                    };
                }
            }
        }, PDFDictionary);

        function BinaryStream(data) {
            var offset = 0, length = 0;
            if (data == null) {
                data = HAS_TYPED_ARRAYS ? new Uint8Array(256) : [];
            } else {
                length = data.length;
            }

            var ensure = HAS_TYPED_ARRAYS ? function (len) {
                if (len >= data.length) {
                    var tmp = new Uint8Array(Math.max(len + 256, data.length * 2));
                    tmp.set(data, 0);
                    data = tmp;
                }
            } : function () { };

            var get = HAS_TYPED_ARRAYS ? function () {
                return new Uint8Array(data.buffer, 0, length);
            } : function () {
                return data;
            };

            var write = HAS_TYPED_ARRAYS ? function (bytes) {
                if (typeof bytes == "string") {
                    return writeString(bytes);
                }
                var len = bytes.length;
                ensure(offset + len);
                data.set(bytes, offset);
                offset += len;
                if (offset > length) {
                    length = offset;
                }
            } : function (bytes) {
                if (typeof bytes == "string") {
                    return writeString(bytes);
                }
                for (var i = 0; i < bytes.length; ++i) {
                    writeByte(bytes[i]);
                }
            };

            var slice = HAS_TYPED_ARRAYS ? function (start, length) {
                if (data.buffer.slice) {
                    return new Uint8Array(data.buffer.slice(start, start + length));
                } else {
                    // IE10
                    var x = new Uint8Array(length);
                    x.set(new Uint8Array(data.buffer, start, length));
                    return x;
                }
            } : function (start, length) {
                return data.slice(start, start + length);
            };

            function eof() {
                return offset >= length;
            }
            function readByte() {
                return offset < length ? data[offset++] : 0;
            }
            function writeByte(b) {
                ensure(offset);
                data[offset++] = b & 0xFF;
                if (offset > length) {
                    length = offset;
                }
            }
            function readShort() {
                return (readByte() << 8) | readByte();
            }
            function writeShort(w) {
                writeByte(w >> 8);
                writeByte(w);
            }
            function readShort_() {
                var w = readShort();
                return w >= 0x8000 ? w - 0x10000 : w;
            }
            function writeShort_(w) {
                writeShort(w < 0 ? w + 0x10000 : w);
            }
            function readLong() {
                return (readShort() * 0x10000) + readShort();
            }
            function writeLong(w) {
                writeShort((w >>> 16) & 0xFFFF);
                writeShort(w & 0xFFFF);
            }
            function readLong_() {
                var w = readLong();
                return w >= 0x80000000 ? w - 0x100000000 : w;
            }
            function writeLong_(w) {
                writeLong(w < 0 ? w + 0x100000000 : w);
            }
            function readFixed() {
                return readLong() / 0x10000;
            }
            function writeFixed(f) {
                writeLong(Math.round(f * 0x10000));
            }
            function readFixed_() {
                return readLong_() / 0x10000;
            }
            function writeFixed_(f) {
                writeLong_(Math.round(f * 0x10000));
            }
            function read(len) {
                return times(len, readByte);
            }
            function readString(len) {
                return String.fromCharCode.apply(String, read(len));
            }
            function writeString(str) {
                for (var i = 0; i < str.length; ++i) {
                    writeByte(str.charCodeAt(i));
                }
            }
            function times(n, reader) {
                for (var ret = new Array(n), i = 0; i < n; ++i) {
                    ret[i] = reader();
                }
                return ret;
            }

            var stream = {
                eof: eof,
                readByte: readByte,
                writeByte: writeByte,
                readShort: readShort,
                writeShort: writeShort,
                readLong: readLong,
                writeLong: writeLong,
                readFixed: readFixed,
                writeFixed: writeFixed,

                // signed numbers.
                readShort_: readShort_,
                writeShort_: writeShort_,
                readLong_: readLong_,
                writeLong_: writeLong_,
                readFixed_: readFixed_,
                writeFixed_: writeFixed_,

                read: read,
                write: write,
                readString: readString,
                writeString: writeString,

                times: times,
                get: get,
                slice: slice,

                offset: function (pos) {
                    if (pos != null) {
                        offset = pos;
                        return stream;
                    }
                    return offset;
                },

                skip: function (nbytes) {
                    offset += nbytes;
                },

                toString: function () {
                    throw new Error("FIX CALLER.  BinaryStream is no longer convertible to string!");
                },

                length: function () { return length; },

                saveExcursion: function (f) {
                    var pos = offset;
                    try {
                        return f();
                    } finally {
                        offset = pos;
                    }
                },

                writeBase64: function (base64) {
                    if (window.atob) {
                        writeString(window.atob(base64));
                    } else {
                        write(BASE64.decode(base64));
                    }
                },
                base64: function () {
                    return BASE64.encode(get());
                }
            };

            return stream;
        }

        function unquote(str) {
            return str.replace(/^\s*(['"])(.*)\1\s*$/, "$2");
        }

        function parseFontDef(fontdef) {
            // XXX: this is very crude for now and buggy.  Proper parsing is quite involved.
            var rx = /^\s*((normal|italic)\s+)?((normal|small-caps)\s+)?((normal|bold|\d+)\s+)?(([0-9.]+)(px|pt))(\/(([0-9.]+)(px|pt)|normal))?\s+(.*?)\s*$/i;
            var m = rx.exec(fontdef);
            if (!m) {
                return { fontSize: 12, fontFamily: "sans-serif" };
            }
            var fontSize = m[8] ? parseInt(m[8], 10) : 12;
            return {
                italic: m[2] && m[2].toLowerCase() == "italic",
                variant: m[4],
                bold: m[6] && /bold|700/i.test(m[6]),
                fontSize: fontSize,
                lineHeight: m[12] ? m[12] == "normal" ? fontSize : parseInt(m[12], 10) : null,
                fontFamily: m[14].split(/\s*,\s*/g).map(unquote)
            };
        }

        function getFontURL(style) {
            function mkFamily(name) {
                if (style.bold) {
                    name += "|bold";
                }
                if (style.italic) {
                    name += "|italic";
                }
                return name.toLowerCase();
            }
            var fontFamily = style.fontFamily;
            var name, url;
            if (fontFamily instanceof Array) {
                for (var i = 0; i < fontFamily.length; ++i) {
                    name = mkFamily(fontFamily[i]);
                    url = FONT_MAPPINGS[name];
                    if (url) {
                        return url;
                    }
                }
            } else {
                url = FONT_MAPPINGS[fontFamily.toLowerCase()];
            }
            if (!url) {
                url = "Times-Roman";
            }
            return url;
        }

        var FONT_MAPPINGS = {
            "serif": "Times-Roman",
            "serif|bold": "Times-Bold",
            "serif|italic": "Times-Italic",
            "serif|bold|italic": "Times-BoldItalic",
            "sans-serif": "Helvetica",
            "sans-serif|bold": "Helvetica-Bold",
            "sans-serif|italic": "Helvetica-Oblique",
            "sans-serif|bold|italic": "Helvetica-BoldOblique",
            "monospace": "Courier",
            "monospace|bold": "Courier-Bold",
            "monospace|italic": "Courier-Oblique",
            "monospace|bold|italic": "Courier-BoldOblique"
        };

        function defineFont(name, url) {
            if (arguments.length == 1) {
                for (var i in name) {
                    defineFont(i, name[i]);
                }
            } else {
                name = name.toLowerCase();
                FONT_MAPPINGS[name] = url;
            }
        }

        /// exports.

        kendo.pdf = {
            Document: PDFDocument,
            BinaryStream: BinaryStream,
            defineFont: defineFont,
            parseFontDef: parseFontDef,
            getFontURL: getFontURL,
            loadFonts: loadFonts,
            loadImages: loadImages,

            TEXT_RENDERING_MODE: {
                fill: 0,
                stroke: 1,
                fillAndStroke: 2,
                invisible: 3,
                fillAndClip: 4,
                strokeAndClip: 5,
                fillStrokeClip: 6,
                clip: 7
            }
        };

        function mmul(a, b) {
            var a1 = a[0], b1 = a[1], c1 = a[2], d1 = a[3], e1 = a[4], f1 = a[5];
            var a2 = b[0], b2 = b[1], c2 = b[2], d2 = b[3], e2 = b[4], f2 = b[5];
            return [
            a1 * a2 + b1 * c2, a1 * b2 + b1 * d2,
            c1 * a2 + d1 * c2, c1 * b2 + d1 * d2,
            e1 * a2 + f1 * c2 + e2, e1 * b2 + f1 * d2 + f2
        ];
        }

        function isIdentityMatrix(m) {
            return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
        }

    })(this, parseFloat);

    (function (global) {

        /*****************************************************************************\
        *
        * The code in this file, although written from scratch, is influenced by the
        * TrueType parser/encoder in PDFKit -- http://pdfkit.org/ (a CoffeeScript
        * library for producing PDF files).
        *
        * PDFKit is (c) Devon Govett 2014 and released under the MIT License.
        *
        \*****************************************************************************/

        "use strict";

        // WARNING: removing the following jshint declaration and turning
        // == into === to make JSHint happy will break functionality.
        /* jshint eqnull:true */
        /* jshint loopfunc:true */
        /* jshint newcap:false */

        function hasOwnProperty(obj, key) {
            return Object.prototype.hasOwnProperty.call(obj, key);
        }

        function sortedKeys(obj) {
            return Object.keys(obj).sort(function (a, b) { return a - b; }).map(parseFloat);
        }

        var PDF = global.kendo.pdf;
        var BinaryStream = PDF.BinaryStream;

        ///

        function Directory(data) {
            this.raw = data;
            this.scalerType = data.readLong();
            this.tableCount = data.readShort();
            this.searchRange = data.readShort();
            this.entrySelector = data.readShort();
            this.rangeShift = data.readShort();

            var tables = this.tables = {};
            for (var i = 0; i < this.tableCount; ++i) {
                var entry = {
                    tag: data.readString(4),
                    checksum: data.readLong(),
                    offset: data.readLong(),
                    length: data.readLong()
                };
                tables[entry.tag] = entry;
            }
        }

        Directory.prototype = {

            readTable: function (name, Ctor) {
                var def = this.tables[name];
                if (!def) {
                    throw new Error("Table " + name + " not found in directory");
                }
                return (this[name] = def.table = new Ctor(this, def));
            },

            render: function (tables) {
                var tableCount = Object.keys(tables).length;

                var maxpow2 = Math.pow(2, Math.floor(Math.log(tableCount) / Math.LN2));
                var searchRange = maxpow2 * 16;
                var entrySelector = Math.floor(Math.log(maxpow2) / Math.LN2);
                var rangeShift = tableCount * 16 - searchRange;

                var out = BinaryStream();
                out.writeLong(this.scalerType);
                out.writeShort(tableCount);
                out.writeShort(searchRange);
                out.writeShort(entrySelector);
                out.writeShort(rangeShift);

                var directoryLength = tableCount * 16;
                var offset = out.offset() + directoryLength;
                var headOffset = null;
                var tableData = BinaryStream();

                for (var tag in tables) {
                    if (hasOwnProperty(tables, tag)) {
                        var table = tables[tag];

                        out.writeString(tag);
                        out.writeLong(this.checksum(table));
                        out.writeLong(offset);
                        out.writeLong(table.length);

                        tableData.write(table);
                        if (tag == "head") {
                            headOffset = offset;
                        }
                        offset += table.length;

                        while (offset % 4) {
                            tableData.writeByte(0);
                            offset++;
                        }
                    }
                }

                out.write(tableData.get());
                var sum = this.checksum(out.get());
                var adjustment = 0xB1B0AFBA - sum;

                out.offset(headOffset + 8);
                out.writeLong(adjustment);
                return out.get();
            },

            checksum: function (data) {
                data = BinaryStream(data);
                var sum = 0;
                while (!data.eof()) {
                    sum += data.readLong();
                }
                return sum & 0xFFFFFFFF;
            }
        };

        function deftable(methods) {
            function Ctor(file, def) {
                this.definition = def;
                this.length = def.length;
                this.offset = def.offset;
                this.file = file;
                this.rawData = file.raw;
                this.parse(file.raw);
            }
            Ctor.prototype.raw = function () {
                return this.rawData.slice(this.offset, this.length);
            };
            for (var i in methods) {
                if (hasOwnProperty(methods, i)) {
                    Ctor[i] = Ctor.prototype[i] = methods[i];
                }
            }
            return Ctor;
        }

        var HeadTable = deftable({
            parse: function (data) {
                data.offset(this.offset);
                this.version = data.readLong();
                this.revision = data.readLong();
                this.checkSumAdjustment = data.readLong();
                this.magicNumber = data.readLong();
                this.flags = data.readShort();
                this.unitsPerEm = data.readShort();
                this.created = data.read(8);
                this.modified = data.read(8);

                this.xMin = data.readShort_();
                this.yMin = data.readShort_();
                this.xMax = data.readShort_();
                this.yMax = data.readShort_();

                this.macStyle = data.readShort();
                this.lowestRecPPEM = data.readShort();
                this.fontDirectionHint = data.readShort_();
                this.indexToLocFormat = data.readShort_();
                this.glyphDataFormat = data.readShort_();
            },
            render: function (indexToLocFormat) {
                var out = BinaryStream();
                out.writeLong(this.version);
                out.writeLong(this.revision);
                out.writeLong(0);       // checksum adjustment; shall be computed later
                out.writeLong(this.magicNumber);
                out.writeShort(this.flags);
                out.writeShort(this.unitsPerEm);
                out.write(this.created);
                out.write(this.modified);
                out.writeShort_(this.xMin);
                out.writeShort_(this.yMin);
                out.writeShort_(this.xMax);
                out.writeShort_(this.yMax);
                out.writeShort(this.macStyle);
                out.writeShort(this.lowestRecPPEM);
                out.writeShort_(this.fontDirectionHint);
                out.writeShort_(indexToLocFormat); // this will depend on the `loca` table
                out.writeShort_(this.glyphDataFormat);
                return out.get();
            }
        });

        var LocaTable = deftable({
            parse: function (data) {
                data.offset(this.offset);
                var format = this.file.head.indexToLocFormat;
                if (format === 0) {
                    this.offsets = data.times(this.length / 2, function () {
                        return 2 * data.readShort();
                    });
                } else {
                    this.offsets = data.times(this.length / 4, data.readLong);
                }
            },
            offsetOf: function (id) {
                return this.offsets[id];
            },
            lengthOf: function (id) {
                return this.offsets[id + 1] - this.offsets[id];
            },
            render: function (offsets) {
                var out = BinaryStream();
                var needsLongFormat = offsets[offsets.length - 1] > 0xFFFF;
                for (var i = 0; i < offsets.length; ++i) {
                    if (needsLongFormat) {
                        out.writeLong(offsets[i]);
                    } else {
                        out.writeShort(offsets[i] / 2);
                    }
                }
                return {
                    format: needsLongFormat ? 1 : 0,
                    table: out.get()
                };
            }
        });

        var HheaTable = deftable({
            parse: function (data) {
                data.offset(this.offset);

                this.version = data.readLong();
                this.ascent = data.readShort_();
                this.descent = data.readShort_();
                this.lineGap = data.readShort_();
                this.advanceWidthMax = data.readShort();
                this.minLeftSideBearing = data.readShort_();
                this.minRightSideBearing = data.readShort_();
                this.xMaxExtent = data.readShort_();
                this.caretSlopeRise = data.readShort_();
                this.caretSlopeRun = data.readShort_();
                this.caretOffset = data.readShort_();

                data.skip(4 * 2);       // reserved

                this.metricDataFormat = data.readShort_();
                this.numOfLongHorMetrics = data.readShort();
            },
            render: function (ids) {
                var out = BinaryStream();
                out.writeLong(this.version);
                out.writeShort_(this.ascent);
                out.writeShort_(this.descent);
                out.writeShort_(this.lineGap);
                out.writeShort(this.advanceWidthMax);
                out.writeShort_(this.minLeftSideBearing);
                out.writeShort_(this.minRightSideBearing);
                out.writeShort_(this.xMaxExtent);
                out.writeShort_(this.caretSlopeRise);
                out.writeShort_(this.caretSlopeRun);
                out.writeShort_(this.caretOffset);

                out.write([0, 0, 0, 0, 0, 0, 0, 0]); // reserved bytes

                out.writeShort_(this.metricDataFormat);
                out.writeShort(ids.length);
                return out.get();
            }
        });

        var MaxpTable = deftable({
            parse: function (data) {
                data.offset(this.offset);
                this.version = data.readLong();
                this.numGlyphs = data.readShort();
                this.maxPoints = data.readShort();
                this.maxContours = data.readShort();
                this.maxComponentPoints = data.readShort();
                this.maxComponentContours = data.readShort();
                this.maxZones = data.readShort();
                this.maxTwilightPoints = data.readShort();
                this.maxStorage = data.readShort();
                this.maxFunctionDefs = data.readShort();
                this.maxInstructionDefs = data.readShort();
                this.maxStackElements = data.readShort();
                this.maxSizeOfInstructions = data.readShort();
                this.maxComponentElements = data.readShort();
                this.maxComponentDepth = data.readShort();
            },
            render: function (glyphIds) {
                var out = BinaryStream();
                out.writeLong(this.version);
                out.writeShort(glyphIds.length);
                out.writeShort(this.maxPoints);
                out.writeShort(this.maxContours);
                out.writeShort(this.maxComponentPoints);
                out.writeShort(this.maxComponentContours);
                out.writeShort(this.maxZones);
                out.writeShort(this.maxTwilightPoints);
                out.writeShort(this.maxStorage);
                out.writeShort(this.maxFunctionDefs);
                out.writeShort(this.maxInstructionDefs);
                out.writeShort(this.maxStackElements);
                out.writeShort(this.maxSizeOfInstructions);
                out.writeShort(this.maxComponentElements);
                out.writeShort(this.maxComponentDepth);
                return out.get();
            }
        });

        var HmtxTable = deftable({
            parse: function (data) {
                data.offset(this.offset);
                var dir = this.file, hhea = dir.hhea;
                this.metrics = data.times(hhea.numOfLongHorMetrics, function () {
                    return {
                        advance: data.readShort(),
                        lsb: data.readShort_()
                    };
                });
                var lsbCount = dir.maxp.numGlyphs - dir.hhea.numOfLongHorMetrics;
                this.leftSideBearings = data.times(lsbCount, data.readShort_);
            },
            forGlyph: function (id) {
                var metrics = this.metrics;
                var n = metrics.length;
                if (id < n) {
                    return metrics[id];
                }
                return {
                    advance: metrics[n - 1].advance,
                    lsb: this.leftSideBearings[id - n]
                };
            },
            render: function (glyphIds) {
                var out = BinaryStream();
                for (var i = 0; i < glyphIds.length; ++i) {
                    var m = this.forGlyph(glyphIds[i]);
                    out.writeShort(m.advance);
                    out.writeShort_(m.lsb);
                }
                return out.get();
            }
        });

        var GlyfTable = (function () {

            function SimpleGlyph(raw) {
                this.raw = raw;
            }
            SimpleGlyph.prototype = {
                compound: false,
                render: function () {
                    return this.raw.get();
                }
            };

            var ARG_1_AND_2_ARE_WORDS = 0x0001;
            var WE_HAVE_A_SCALE = 0x0008;
            var MORE_COMPONENTS = 0x0020;
            var WE_HAVE_AN_X_AND_Y_SCALE = 0x0040;
            var WE_HAVE_A_TWO_BY_TWO = 0x0080;
            var WE_HAVE_INSTRUCTIONS = 0x0100;

            function CompoundGlyph(data) {
                this.raw = data;
                var ids = this.glyphIds = [];
                var offsets = this.idOffsets = [];
                while (true) {
                    var flags = data.readShort();
                    offsets.push(data.offset());
                    ids.push(data.readShort());

                    if (!(flags & MORE_COMPONENTS)) {
                        break;
                    }

                    data.skip(flags & ARG_1_AND_2_ARE_WORDS ? 4 : 2);

                    if (flags & WE_HAVE_A_TWO_BY_TWO) {
                        data.skip(8);
                    } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
                        data.skip(4);
                    } else if (flags & WE_HAVE_A_SCALE) {
                        data.skip(2);
                    }
                }
            }

            CompoundGlyph.prototype = {
                compound: true,
                render: function (old2new) {
                    var out = BinaryStream(this.raw.get());
                    for (var i = 0; i < this.glyphIds.length; ++i) {
                        var id = this.glyphIds[i];
                        out.offset(this.idOffsets[i]);
                        out.writeShort(old2new[id]);
                    }
                    return out.get();
                }
            };

            return deftable({
                parse: function (data) {
                    this.cache = {};
                },
                glyphFor: function (id) {
                    var cache = this.cache;
                    if (hasOwnProperty(cache, id)) {
                        return cache[id];
                    }

                    var loca = this.file.loca;
                    var length = loca.lengthOf(id);

                    if (length === 0) {
                        return (cache[id] = null);
                    }

                    var data = this.rawData;
                    var offset = this.offset + loca.offsetOf(id);
                    var raw = BinaryStream(data.slice(offset, length));

                    var numberOfContours = raw.readShort_();
                    var xMin = raw.readShort_();
                    var yMin = raw.readShort_();
                    var xMax = raw.readShort_();
                    var yMax = raw.readShort_();

                    var glyph = cache[id] = numberOfContours == -1 ? new CompoundGlyph(raw) : new SimpleGlyph(raw);

                    glyph.numberOfContours = numberOfContours;
                    glyph.xMin = xMin;
                    glyph.yMin = yMin;
                    glyph.xMax = xMax;
                    glyph.yMax = yMax;

                    return glyph;
                },
                render: function (glyphs, oldIds, old2new) {
                    var out = BinaryStream(), offsets = [];
                    for (var i = 0; i < oldIds.length; ++i) {
                        var id = oldIds[i];
                        var glyph = glyphs[id];
                        offsets.push(out.offset());
                        if (glyph) {
                            out.write(glyph.render(old2new));
                        }
                    }
                    offsets.push(out.offset());
                    return {
                        table: out.get(),
                        offsets: offsets
                    };
                }
            });

        } ());

        var NameTable = (function () {

            function NameEntry(text, entry) {
                this.text = text;
                this.length = text.length;
                this.platformID = entry.platformID;
                this.platformSpecificID = entry.platformSpecificID;
                this.languageID = entry.languageID;
                this.nameID = entry.nameID;
            }

            return deftable({
                parse: function (data) {
                    data.offset(this.offset);
                    var format = data.readShort();
                    var count = data.readShort();
                    var stringOffset = this.offset + data.readShort();
                    var nameRecords = data.times(count, function () {
                        return {
                            platformID: data.readShort(),
                            platformSpecificID: data.readShort(),
                            languageID: data.readShort(),
                            nameID: data.readShort(),
                            length: data.readShort(),
                            offset: data.readShort() + stringOffset
                        };
                    });
                    var strings = this.strings = {};
                    for (var i = 0; i < nameRecords.length; ++i) {
                        var rec = nameRecords[i];
                        data.offset(rec.offset);
                        var text = data.readString(rec.length);
                        if (!strings[rec.nameID]) {
                            strings[rec.nameID] = [];
                        }
                        strings[rec.nameID].push(new NameEntry(text, rec));
                    }
                    this.postscriptEntry = strings[6][0];
                    this.postscriptName = this.postscriptEntry.text.replace(/[^\x20-\x7F]/g, "");
                },

                render: function (psName) {
                    var strings = this.strings;
                    var strCount = 0;
                    for (var i in strings) {
                        if (hasOwnProperty(strings, i)) {
                            strCount += strings[i].length;
                        }
                    }
                    var out = BinaryStream();
                    var strTable = BinaryStream();

                    out.writeShort(0);  // format
                    out.writeShort(strCount);
                    out.writeShort(6 + 12 * strCount); // stringOffset

                    for (i in strings) {
                        if (hasOwnProperty(strings, i)) {
                            var list = i == 6 ? [
                        new NameEntry(psName, this.postscriptEntry)
                    ] : strings[i];
                            for (var j = 0; j < list.length; ++j) {
                                var str = list[j];
                                out.writeShort(str.platformID);
                                out.writeShort(str.platformSpecificID);
                                out.writeShort(str.languageID);
                                out.writeShort(str.nameID);
                                out.writeShort(str.length);
                                out.writeShort(strTable.offset());

                                strTable.writeString(str.text);
                            }
                        }
                    }

                    out.write(strTable.get());

                    return out.get();
                }
            });

        })();

        var PostTable = (function () {

            var POSTSCRIPT_GLYPHS = ".notdef .null nonmarkingreturn space exclam quotedbl numbersign dollar percent ampersand quotesingle parenleft parenright asterisk plus comma hyphen period slash zero one two three four five six seven eight nine colon semicolon less equal greater question at A B C D E F G H I J K L M N O P Q R S T U V W X Y Z bracketleft backslash bracketright asciicircum underscore grave a b c d e f g h i j k l m n o p q r s t u v w x y z braceleft bar braceright asciitilde Adieresis Aring Ccedilla Eacute Ntilde Odieresis Udieresis aacute agrave acircumflex adieresis atilde aring ccedilla eacute egrave ecircumflex edieresis iacute igrave icircumflex idieresis ntilde oacute ograve ocircumflex odieresis otilde uacute ugrave ucircumflex udieresis dagger degree cent sterling section bullet paragraph germandbls registered copyright trademark acute dieresis notequal AE Oslash infinity plusminus lessequal greaterequal yen mu partialdiff summation product pi integral ordfeminine ordmasculine Omega ae oslash questiondown exclamdown logicalnot radical florin approxequal Delta guillemotleft guillemotright ellipsis nonbreakingspace Agrave Atilde Otilde OE oe endash emdash quotedblleft quotedblright quoteleft quoteright divide lozenge ydieresis Ydieresis fraction currency guilsinglleft guilsinglright fi fl daggerdbl periodcentered quotesinglbase quotedblbase perthousand Acircumflex Ecircumflex Aacute Edieresis Egrave Iacute Icircumflex Idieresis Igrave Oacute Ocircumflex apple Ograve Uacute Ucircumflex Ugrave dotlessi circumflex tilde macron breve dotaccent ring cedilla hungarumlaut ogonek caron Lslash lslash Scaron scaron Zcaron zcaron brokenbar Eth eth Yacute yacute Thorn thorn minus multiply onesuperior twosuperior threesuperior onehalf onequarter threequarters franc Gbreve gbreve Idotaccent Scedilla scedilla Cacute cacute Ccaron ccaron dcroat".split(/\s+/g);

            return deftable({
                parse: function (data) {
                    data.offset(this.offset);

                    this.format = data.readLong();
                    this.italicAngle = data.readFixed_();
                    this.underlinePosition = data.readShort_();
                    this.underlineThickness = data.readShort_();
                    this.isFixedPitch = data.readLong();
                    this.minMemType42 = data.readLong();
                    this.maxMemType42 = data.readLong();
                    this.minMemType1 = data.readLong();
                    this.maxMemType1 = data.readLong();

                    var numberOfGlyphs;

                    switch (this.format) {
                        case 0x00010000:
                        case 0x00030000:
                            break;

                        case 0x00020000:
                            numberOfGlyphs = data.readShort();
                            this.glyphNameIndex = data.times(numberOfGlyphs, data.readShort);
                            this.names = [];
                            var limit = this.offset + this.length;
                            while (data.offset() < limit) {
                                this.names.push(data.readString(data.readByte()));
                            }
                            break;

                        case 0x00025000:
                            numberOfGlyphs = data.readShort();
                            this.offsets = data.read(numberOfGlyphs);
                            break;

                        case 0x00040000:
                            this.map = data.times(this.file.maxp.numGlyphs, data.readShort);
                            break;
                    }
                },
                glyphFor: function (code) {
                    switch (this.format) {
                        case 0x00010000:
                            return POSTSCRIPT_GLYPHS[code] || ".notdef";

                        case 0x00020000:
                            var index = this.glyphNameIndex[code];
                            if (index < POSTSCRIPT_GLYPHS.length) {
                                return POSTSCRIPT_GLYPHS[index];
                            }
                            return this.names[index - POSTSCRIPT_GLYPHS.length] || ".notdef";

                        case 0x00025000:

                        case 0x00030000:
                            return ".notdef";

                        case 0x00040000:
                            return this.map[code] || 0xFFFF;
                    }
                },
                render: function (mapping) {
                    if (this.format == 0x00030000) {
                        return this.raw();
                    }

                    // keep original header, but set format to 2.0
                    var out = BinaryStream(this.rawData.slice(this.offset, 32));
                    out.writeLong(0x00020000);
                    out.offset(32);

                    var indexes = [];
                    var strings = [];

                    for (var i = 0; i < mapping.length; ++i) {
                        var id = mapping[i];
                        var post = this.glyphFor(id);
                        var index = POSTSCRIPT_GLYPHS.indexOf(post);
                        if (index >= 0) {
                            indexes.push(index);
                        } else {
                            indexes.push(POSTSCRIPT_GLYPHS.length + strings.length);
                            strings.push(post);
                        }
                    }

                    out.writeShort(mapping.length);

                    for (i = 0; i < indexes.length; ++i) {
                        out.writeShort(indexes[i]);
                    }

                    for (i = 0; i < strings.length; ++i) {
                        out.writeByte(strings[i].length);
                        out.writeString(strings[i]);
                    }

                    return out.get();
                }
            });
        })();

        var CmapTable = (function () {

            function CmapEntry(data, offset) {
                var self = this;
                self.platformID = data.readShort();
                self.platformSpecificID = data.readShort();
                self.offset = offset + data.readLong();

                data.saveExcursion(function () {
                    data.offset(self.offset);
                    self.format = data.readShort();
                    self.length = data.readShort();
                    self.language = data.readShort();

                    self.isUnicode = (
                self.platformID == 3 && self.platformSpecificID == 1 && self.format == 4
            ) || (
                self.platformID === 0 && self.format == 4
            );

                    self.codeMap = {};
                    switch (self.format) {
                        case 0:
                            for (var i = 0; i < 256; ++i) {
                                self.codeMap[i] = data.readByte();
                            }
                            break;

                        case 4:
                            var segCount = data.readShort() / 2;

                            data.skip(6);       // searchRange, entrySelector, rangeShift
                            var endCode = data.times(segCount, data.readShort);
                            data.skip(2);       // reserved pad
                            var startCode = data.times(segCount, data.readShort);
                            var idDelta = data.times(segCount, data.readShort_);
                            var idRangeOffset = data.times(segCount, data.readShort);

                            var count = (self.length + self.offset - data.offset()) / 2;
                            var glyphIds = data.times(count, data.readShort);

                            for (i = 0; i < segCount; ++i) {
                                var start = startCode[i], end = endCode[i];
                                for (var code = start; code <= end; ++code) {
                                    var glyphId;
                                    if (idRangeOffset[i] === 0) {
                                        glyphId = code + idDelta[i];
                                    } else {
                                        ///
                                        // When non-zero, idRangeOffset contains for each segment the byte offset of the Glyph ID
                                        // into the glyphIds table, from the *current* `i` cell of idRangeOffset.  In other words,
                                        // this offset spans from the first into the second array.  This works, because the arrays
                                        // are consecutive in the TTF file:
                                        //
                                        //     [ ...idRangeOffset... ][ ...glyphIds... ]
                                        //       ...... 48 ......       .... ID ....
                                        //              ^----- 48 bytes -----^
                                        //
                                        // (but I can't stop wondering why is it not just a plain index, possibly incremented by 1
                                        // so that we can have that special `zero` value.)
                                        //
                                        // The elements of idRangeOffset are even numbers, because both arrays contain 16-bit words,
                                        // yet the offset is in bytes.  That is why we divide it by 2.  Then we subtract the
                                        // remaining segments (segCount-i), and add the code-start offset, to which we need to add
                                        // the corresponding delta to get the actual glyph ID.
                                        ///
                                        var index = idRangeOffset[i] / 2 - (segCount - i) + (code - start);
                                        glyphId = glyphIds[index] || 0;
                                        if (glyphId !== 0) {
                                            glyphId += idDelta[i];
                                        }
                                    }
                                    self.codeMap[code] = glyphId & 0xFFFF;
                                }
                            }
                    }
                });
            }

            function renderCharmap(ncid2ogid, ogid2ngid) {
                var codes = sortedKeys(ncid2ogid);
                var startCodes = [];
                var endCodes = [];
                var last = null;
                var diff = null;

                function new_gid(charcode) {
                    return ogid2ngid[ncid2ogid[charcode]];
                }

                for (var i = 0; i < codes.length; ++i) {
                    var code = codes[i];
                    var gid = new_gid(code);
                    var delta = gid - code;
                    if (last == null || delta !== diff) {
                        if (last) {
                            endCodes.push(last);
                        }
                        startCodes.push(code);
                        diff = delta;
                    }
                    last = code;
                }

                if (last) {
                    endCodes.push(last);
                }
                endCodes.push(0xFFFF);
                startCodes.push(0xFFFF);

                var segCount = startCodes.length;
                var segCountX2 = segCount * 2;
                var searchRange = 2 * Math.pow(2, Math.floor(Math.log(segCount) / Math.LN2));
                var entrySelector = Math.log(searchRange / 2) / Math.LN2;
                var rangeShift = segCountX2 - searchRange;

                var deltas = [];
                var rangeOffsets = [];
                var glyphIds = [];

                for (i = 0; i < segCount; ++i) {
                    var startCode = startCodes[i];
                    var endCode = endCodes[i];
                    if (startCode == 0xFFFF) {
                        deltas.push(0);
                        rangeOffsets.push(0);
                        break;
                    }
                    var startGlyph = new_gid(startCode);
                    if (startCode - startGlyph >= 0x8000) {
                        deltas.push(0);
                        rangeOffsets.push(2 * (glyphIds.length + segCount - i));
                        for (var j = startCode; j <= endCode; ++j) {
                            glyphIds.push(new_gid(j));
                        }
                    } else {
                        deltas.push(startGlyph - startCode);
                        rangeOffsets.push(0);
                    }
                }

                var out = BinaryStream();

                out.writeShort(3);      // platformID
                out.writeShort(1);      // platformSpecificID
                out.writeLong(12);      // offset
                out.writeShort(4);      // format
                out.writeShort(16 + segCount * 8 + glyphIds.length * 2); // length
                out.writeShort(0);      // language
                out.writeShort(segCountX2);
                out.writeShort(searchRange);
                out.writeShort(entrySelector);
                out.writeShort(rangeShift);

                endCodes.forEach(out.writeShort);
                out.writeShort(0);      // reserved pad
                startCodes.forEach(out.writeShort);
                deltas.forEach(out.writeShort_);
                rangeOffsets.forEach(out.writeShort);
                glyphIds.forEach(out.writeShort);

                return out.get();
            }

            return deftable({
                parse: function (data) {
                    var self = this;
                    var offset = self.offset;
                    data.offset(offset);

                    self.version = data.readShort();
                    var tableCount = data.readShort();
                    self.unicodeEntry = null;
                    self.tables = data.times(tableCount, function () {
                        var entry = new CmapEntry(data, offset);
                        if (entry.isUnicode) {
                            self.unicodeEntry = entry;
                        }
                        return entry;
                    });
                },
                render: function (ncid2ogid, ogid2ngid) {
                    var out = BinaryStream();
                    out.writeShort(0);  // version
                    out.writeShort(1);  // tableCount
                    out.write(renderCharmap(ncid2ogid, ogid2ngid));
                    return out.get();
                },
                getUnicodeEntry: function () {
                    if (!this.unicodeEntry) {
                        throw new Error("Font doesn't have an Unicode encoding");
                    }
                    return this.unicodeEntry;
                }
            });

        })();

        var OS2Table = deftable({
            parse: function (data) {
                data.offset(this.offset);
                this.version = data.readShort();
                this.averageCharWidth = data.readShort_();
                this.weightClass = data.readShort();
                this.widthClass = data.readShort();
                this.type = data.readShort();
                this.ySubscriptXSize = data.readShort_();
                this.ySubscriptYSize = data.readShort_();
                this.ySubscriptXOffset = data.readShort_();
                this.ySubscriptYOffset = data.readShort_();
                this.ySuperscriptXSize = data.readShort_();
                this.ySuperscriptYSize = data.readShort_();
                this.ySuperscriptXOffset = data.readShort_();
                this.ySuperscriptYOffset = data.readShort_();
                this.yStrikeoutSize = data.readShort_();
                this.yStrikeoutPosition = data.readShort_();
                this.familyClass = data.readShort_();

                this.panose = data.times(10, data.readByte);
                this.charRange = data.times(4, data.readLong);

                this.vendorID = data.readString(4);
                this.selection = data.readShort();
                this.firstCharIndex = data.readShort();
                this.lastCharIndex = data.readShort();

                if (this.version > 0) {
                    this.ascent = data.readShort_();
                    this.descent = data.readShort_();
                    this.lineGap = data.readShort_();
                    this.winAscent = data.readShort();
                    this.winDescent = data.readShort();
                    this.codePageRange = data.times(2, data.readLong);

                    if (this.version > 1) {
                        this.xHeight = data.readShort();
                        this.capHeight = data.readShort();
                        this.defaultChar = data.readShort();
                        this.breakChar = data.readShort();
                        this.maxContext = data.readShort();
                    }
                }
            },
            render: function () {
                return this.raw();
            }
        });

        var subsetTag = 100000;

        function nextSubsetTag() {
            var ret = "", n = subsetTag + "";
            for (var i = 0; i < n.length; ++i) {
                ret += String.fromCharCode(n.charCodeAt(i) - 48 + 65);
            }
            ++subsetTag;
            return ret;
        }

        function Subfont(font) {
            this.font = font;
            this.subset = {};
            this.unicodes = {};
            this.ogid2ngid = { 0: 0 };
            this.ngid2ogid = { 0: 0 };
            this.ncid2ogid = {};
            this.next = this.firstChar = 1;
            this.nextGid = 1;
            this.psName = nextSubsetTag() + "+" + this.font.psName;
        }

        Subfont.prototype = {
            use: function (ch) {
                var code;
                if (typeof ch == "string") {
                    var ret = "";
                    for (var i = 0; i < ch.length; ++i) {
                        code = this.use(ch.charCodeAt(i));
                        ret += String.fromCharCode(code);
                    }
                    return ret;
                }
                code = this.unicodes[ch];
                if (!code) {
                    code = this.next++;
                    this.subset[code] = ch;
                    this.unicodes[ch] = code;

                    // generate new GID (glyph ID) and maintain newGID ->
                    // oldGID and back mappings
                    var old_gid = this.font.cmap.getUnicodeEntry().codeMap[ch];
                    if (old_gid) {
                        this.ncid2ogid[code] = old_gid;
                        if (this.ogid2ngid[old_gid] == null) {
                            var new_gid = this.nextGid++;
                            this.ogid2ngid[old_gid] = new_gid;
                            this.ngid2ogid[new_gid] = old_gid;
                        }
                    }
                }
                return code;
            },
            encodeText: function (text) {
                return this.use(text);
            },
            glyphIds: function () {
                return sortedKeys(this.ogid2ngid);
            },
            glyphsFor: function (glyphIds, result) {
                if (!result) {
                    result = {};
                }
                for (var i = 0; i < glyphIds.length; ++i) {
                    var id = glyphIds[i];
                    if (!result[id]) {
                        var glyph = result[id] = this.font.glyf.glyphFor(id);
                        if (glyph && glyph.compound) {
                            this.glyphsFor(glyph.glyphIds, result);
                        }
                    }
                }
                return result;
            },
            render: function () {
                var glyphs = this.glyphsFor(this.glyphIds());

                // add missing sub-glyphs
                for (var old_gid in glyphs) {
                    if (hasOwnProperty(glyphs, old_gid)) {
                        old_gid = parseInt(old_gid, 10);
                        if (this.ogid2ngid[old_gid] == null) {
                            var new_gid = this.nextGid++;
                            this.ogid2ngid[old_gid] = new_gid;
                            this.ngid2ogid[new_gid] = old_gid;
                        }
                    }
                }

                // must obtain old_gid_ids in an order matching sorted
                // new_gid_ids
                var new_gid_ids = sortedKeys(this.ngid2ogid);
                var old_gid_ids = new_gid_ids.map(function (id) {
                    return this.ngid2ogid[id];
                }, this);

                var font = this.font;
                var glyf = font.glyf.render(glyphs, old_gid_ids, this.ogid2ngid);
                var loca = font.loca.render(glyf.offsets);

                this.lastChar = this.next - 1;

                var tables = {
                    "cmap": CmapTable.render(this.ncid2ogid, this.ogid2ngid),
                    "glyf": glyf.table,
                    "loca": loca.table,
                    "hmtx": font.hmtx.render(old_gid_ids),
                    "hhea": font.hhea.render(old_gid_ids),
                    "maxp": font.maxp.render(old_gid_ids),
                    "post": font.post.render(old_gid_ids),
                    "name": font.name.render(this.psName),
                    "head": font.head.render(loca.format),
                    "OS/2": font.os2.render()
                };

                return this.font.directory.render(tables);
            },
            cidToGidMap: function () {
                var out = BinaryStream(), len = 0;
                for (var cid = this.firstChar; cid < this.next; ++cid) {
                    while (len < cid) {
                        out.writeShort(0);
                        len++;
                    }
                    var old_gid = this.ncid2ogid[cid];
                    if (old_gid) {
                        var new_gid = this.ogid2ngid[old_gid];
                        out.writeShort(new_gid);
                    } else {
                        out.writeShort(0);
                    }
                    len++;
                }
                return out.get();
            }
        };

        function TTFFont(rawData, name) {
            var self = this;
            var data = self.contents = BinaryStream(rawData);
            if (data.readString(4) == "ttcf") {
                if (!name) {
                    throw new Error("Must specify a name for TTC files");
                }
                var version = data.readLong();
                var numFonts = data.readLong();
                for (var i = 0; i < numFonts; ++i) {
                    var offset = data.readLong();
                    data.saveExcursion(function () {
                        data.offset(offset);
                        self.parse();
                    });
                    if (self.psName == name) {
                        return;
                    }
                }
                throw new Error("Font " + name + " not found in collection");
            } else {
                data.offset(0);
                self.parse();
            }
        }

        TTFFont.prototype = {
            parse: function () {
                var dir = this.directory = new Directory(this.contents);

                this.head = dir.readTable("head", HeadTable);
                this.loca = dir.readTable("loca", LocaTable);
                this.hhea = dir.readTable("hhea", HheaTable);
                this.maxp = dir.readTable("maxp", MaxpTable);
                this.hmtx = dir.readTable("hmtx", HmtxTable);
                this.glyf = dir.readTable("glyf", GlyfTable);
                this.name = dir.readTable("name", NameTable);
                this.post = dir.readTable("post", PostTable);
                this.cmap = dir.readTable("cmap", CmapTable);
                this.os2 = dir.readTable("OS/2", OS2Table);

                this.psName = this.name.postscriptName;
                this.ascent = this.os2.ascent || this.hhea.ascent;
                this.descent = this.os2.descent || this.hhea.descent;
                this.lineGap = this.os2.lineGap || this.hhea.lineGap;
                this.scale = 1000 / this.head.unitsPerEm;
            },
            widthOfGlyph: function (glyph) {
                return this.hmtx.forGlyph(glyph).advance * this.scale;
            },
            makeSubset: function () {
                return new Subfont(this);
            }
        };

        PDF.TTFFont = TTFFont;

    })(this);



    (function (kendo) {

        kendo.PDFMixin = {
            extend: function (proto) {
                proto.events.push("pdfExport");
                proto.options.pdf = this.options;
                proto.saveAsPDF = this.saveAsPDF;
            },
            options: {
                fileName: "Export.pdf",
                proxyURL: "",

                // paperSize can be an usual name, i.e. "A4", or an array of two Number-s specifying the
                // width/height in points (1pt = 1/72in), or strings including unit, i.e. "10mm".  Supported
                // units are "mm", "cm", "in" and "pt".  The default "auto" means paper size is determined
                // by content.
                paperSize: "auto",

                // pass true to reverse the paper dimensions if needed such that width is the larger edge.
                // doesn't make much sense with "auto" paperSize.
                landscape: false,

                // pass an object containing { left, top, bottom, right } margins (numbers of strings with
                // units).
                margin: null,

                // optional information for the PDF Info dictionary; all strings except for the date.
                title: null,
                author: null,
                subject: null,
                keywords: null,
                creator: "Kendo UI PDF Generator",
                date: null        // CreationDate; must be a Date object, defaults to new Date()
            },
            saveAsPDF: function () {
                if (this.trigger("pdfExport")) {
                    return;
                }

                var options = this.options.pdf;

                kendo.drawing.drawDOM(this.wrapper[0])
        .then(function (root) {
            return kendo.drawing.exportPDF(root, options);
        })
        .done(function (dataURI) {
            kendo.saveAs({
                dataURI: dataURI,
                fileName: options.fileName,
                proxyURL: options.proxyURL,
                forceProxy: options.forceProxy
            });
        });
            }
        };

    })(kendo);



    (function (kendo, $) {

        "use strict";

        // WARNING: removing the following jshint declaration and turning
        // == into === to make JSHint happy will break functionality.
        /*jshint eqnull:true  */

        var drawing = kendo.drawing;
        var geo = kendo.geometry;
        var Color = drawing.Color;

        function PDF() {
            if (!kendo.pdf) {
                throw new Error("kendo.pdf.js is not loaded");
            }
            return kendo.pdf;
        }

        var DASH_PATTERNS = {
            dash: [4],
            dashDot: [4, 2, 1, 2],
            dot: [1, 2],
            longDash: [8, 2],
            longDashDot: [8, 2, 1, 2],
            longDashDotDot: [8, 2, 1, 2, 1, 2],
            solid: []
        };

        var LINE_CAP = {
            butt: 0,
            round: 1,
            square: 2
        };

        var LINE_JOIN = {
            miter: 0,
            round: 1,
            bevel: 2
        };

        function render(group, callback) {
            var fonts = [], images = [], options = group.options;

            var tmp = optimize(group);
            var bbox = tmp.bbox;
            group = tmp.root;

            group.traverse(function (element) {
                dispatch({
                    Image: function (element) {
                        if (images.indexOf(element.src()) < 0) {
                            images.push(element.src());
                        }
                    },
                    Text: function (element) {
                        var style = PDF().parseFontDef(element.options.font);
                        var url = PDF().getFontURL(style);
                        if (fonts.indexOf(url) < 0) {
                            fonts.push(url);
                        }
                    }
                }, element);
            });

            function getOption(name, defval) {
                if (options.pdf && options.pdf[name] != null) {
                    return options.pdf[name];
                }
                return defval;
            }

            function doIt() {
                if (--count > 0) {
                    return;
                }
                var paperSize = getOption("paperSize", "auto"), addMargin = false;
                if (paperSize == "auto") {
                    if (bbox) {
                        var size = bbox.getSize();
                        paperSize = [size.width, size.height];
                        addMargin = true;
                        var origin = bbox.getOrigin();
                        var tmp = new drawing.Group();
                        tmp.transform(new geo.Matrix(1, 0, 0, 1, -origin.x, -origin.y));
                        tmp.append(group);

                        group = tmp;
                    } else {
                        paperSize = "A4";
                    }
                }
                var pdf = new (PDF().Document)({
                    paperSize: paperSize,
                    landscape: getOption("landscape", false),
                    margin: getOption("margin"),
                    addMargin: addMargin,
                    title: getOption("title"),
                    author: getOption("author"),
                    subject: getOption("subject"),
                    keywords: getOption("keywords"),
                    creator: getOption("creator"),
                    date: getOption("date")
                });
                var page = pdf.addPage();
                drawElement(group, page, pdf);
                callback(pdf.render(), pdf, page);
            }

            var count = 2;
            PDF().loadFonts(fonts, doIt);
            PDF().loadImages(images, doIt);
        }

        function toDataURL(group, callback) {
            render(group, function (data) {
                callback("data:application/pdf;base64," + data.base64());
            });
        }

        function toBlob(group, callback) {
            render(group, function (data) {
                callback(new Blob([data.get()], { type: "application/pdf" }));
            });
        }

        function saveAs(group, filename, proxy, callback) {
            // XXX: Safari has Blob, but does not support the download attribute
            //      so we'd end up converting to dataURL and using the proxy anyway.
            if (window.Blob && !kendo.support.browser.safari) {
                toBlob(group, function (blob) {
                    kendo.saveAs({ dataURI: blob, fileName: filename });
                    if (callback) {
                        callback(blob);
                    }
                });
            } else {
                toDataURL(group, function (dataURL) {
                    kendo.saveAs({ dataURI: dataURL, fileName: filename, proxyURL: proxy });
                    if (callback) {
                        callback(dataURL);
                    }
                });
            }
        }

        function dispatch(handlers, element) {
            var handler = handlers[element.nodeType];
            if (handler) {
                return handler.call.apply(handler, arguments);
            }
            return element;
        }

        function drawElement(element, page, pdf) {
            if (element.DEBUG) {
                page.comment(element.DEBUG);
            }

            var transform = element.transform();
            var opacity = element.opacity();

            page.save();

            if (opacity != null && opacity < 1) {
                page.setOpacity(opacity);
            }

            setStrokeOptions(element, page, pdf);
            setFillOptions(element, page, pdf);
            setClipping(element, page, pdf);

            if (transform) {
                var m = transform.matrix();
                page.transform(m.a, m.b, m.c, m.d, m.e, m.f);
            }

            dispatch({
                Path: drawPath,
                MultiPath: drawMultiPath,
                Circle: drawCircle,
                Arc: drawArc,
                Text: drawText,
                Image: drawImage,
                Group: drawGroup
            }, element, page, pdf);

            page.restore();
        }

        function setStrokeOptions(element, page, pdf) {
            var stroke = element.stroke && element.stroke();
            if (!stroke) {
                return;
            }

            var color = stroke.color;
            if (color) {
                color = parseColor(color);
                if (color == null) {
                    return; // no stroke
                }
                page.setStrokeColor(color.r, color.g, color.b);
                if (color.a != 1) {
                    page.setStrokeOpacity(color.a);
                }
            }

            var width = stroke.width;
            if (width != null) {
                if (width === 0) {
                    return; // no stroke
                }
                page.setLineWidth(width);
            }

            var dashType = stroke.dashType;
            if (dashType) {
                page.setDashPattern(DASH_PATTERNS[dashType], 0);
            }

            var lineCap = stroke.lineCap;
            if (lineCap) {
                page.setLineCap(LINE_CAP[lineCap]);
            }

            var lineJoin = stroke.lineJoin;
            if (lineJoin) {
                page.setLineJoin(LINE_JOIN[lineJoin]);
            }

            var opacity = stroke.opacity;
            if (opacity != null) {
                page.setStrokeOpacity(opacity);
            }
        }

        function setFillOptions(element, page, pdf) {
            var fill = element.fill && element.fill();
            if (!fill) {
                return;
            }

            if (fill instanceof drawing.Gradient) {
                return;
            }

            var color = fill.color;
            if (color) {
                color = parseColor(color);
                if (color == null) {
                    return; // no fill
                }
                page.setFillColor(color.r, color.g, color.b);
                if (color.a != 1) {
                    page.setFillOpacity(color.a);
                }
            }

            var opacity = fill.opacity;
            if (opacity != null) {
                page.setFillOpacity(opacity);
            }
        }

        function setClipping(element, page, pdf) {
            // XXX: only Path supported at the moment.
            var clip = element.clip();
            if (clip) {
                _drawPath(clip, page, pdf);
                page.clip();
                // page.setStrokeColor(Math.random(), Math.random(), Math.random());
                // page.setLineWidth(1);
                // page.stroke();
            }
        }

        function shouldDraw(thing) {
            return (thing &&
                (thing instanceof drawing.Gradient ||
                 (thing.color && !/^(none|transparent)$/i.test(thing.color) &&
                  (thing.width == null || thing.width > 0) &&
                  (thing.opacity == null || thing.opacity > 0))));
        }

        function maybeGradient(element, page, pdf, stroke) {
            var fill = element.fill();
            if (fill instanceof drawing.Gradient) {
                if (stroke) {
                    page.stroke();
                } else {
                    // XXX: could optimize: the already rendered path is
                    // useless in this case.
                    page.nop();
                }
                if (!(fill instanceof drawing.LinearGradient)) {
                    // XXX:
                    if (window.console && window.console.log) {
                        window.console.log("Radial gradients are not yet supported in PDF output.");
                    }
                    return;
                }
                var start = { x: fill.start().x, y: fill.start().y };
                var end = { x: fill.end().x, y: fill.end().y };
                var gradient = {
                    type: "linear",
                    start: start,
                    end: end,
                    userSpace: fill.userSpace(),
                    stops: fill.stops.elements().map(function (stop) {
                        var offset = stop.offset();
                        if (/%$/.test(offset)) {
                            offset = parseFloat(offset) / 100;
                        } else {
                            offset = parseFloat(offset);
                        }
                        var color = parseColor(stop.color());
                        color.a *= stop.opacity();
                        return {
                            offset: offset,
                            color: color
                        };
                    })
                };
                var box = element.rawBBox();
                var tl = box.topLeft(), size = box.getSize();
                box = {
                    left: tl.x,
                    top: tl.y,
                    width: size.width,
                    height: size.height
                };
                page.gradient(gradient, box);
                return true;
            }
        }

        function maybeFillStroke(element, page, pdf) {
            if (shouldDraw(element.fill()) && shouldDraw(element.stroke())) {
                if (!maybeGradient(element, page, pdf, true)) {
                    page.fillStroke();
                }
            } else if (shouldDraw(element.fill())) {
                if (!maybeGradient(element, page, pdf, false)) {
                    page.fill();
                }
            } else if (shouldDraw(element.stroke())) {
                page.stroke();
            } else {
                // we should not get here; the path should have been
                // optimized away.  but let's be prepared.
                page.nop();
            }
        }

        function maybeDrawRect(path, page, pdf) {
            var segments = path.segments;
            if (segments.length == 4 && path.options.closed) {
                // detect if this path looks like a rectangle parallel to the axis
                var a = [];
                for (var i = 0; i < segments.length; ++i) {
                    if (segments[i].controlIn()) { // has curve?
                        return false;
                    }
                    a[i] = segments[i].anchor();
                }
                // it's a rectangle if the y/x/y/x or x/y/x/y coords of
                // consecutive points are the same.
                var isRect = (
                a[0].y == a[1].y && a[1].x == a[2].x && a[2].y == a[3].y && a[3].x == a[0].x
            ) || (
                a[0].x == a[1].x && a[1].y == a[2].y && a[2].x == a[3].x && a[3].y == a[0].y
            );
                if (isRect) {
                    // this saves a bunch of instructions in PDF:
                    // moveTo, lineTo, lineTo, lineTo, close -> rect.
                    page.rect(a[0].x, a[0].y,
                          a[2].x - a[0].x /*width*/,
                          a[2].y - a[0].y /*height*/);
                    return true;
                }
            }
        }

        function _drawPath(element, page, pdf) {
            var segments = element.segments;
            if (segments.length === 0) {
                return;
            }
            if (!maybeDrawRect(element, page, pdf)) {
                for (var prev, i = 0; i < segments.length; ++i) {
                    var seg = segments[i];
                    var anchor = seg.anchor();
                    if (!prev) {
                        page.moveTo(anchor.x, anchor.y);
                    } else {
                        var prevOut = prev.controlOut();
                        var controlIn = seg.controlIn();
                        if (prevOut && controlIn) {
                            page.bezier(
                            prevOut.x, prevOut.y,
                            controlIn.x, controlIn.y,
                            anchor.x, anchor.y
                        );
                        } else {
                            page.lineTo(anchor.x, anchor.y);
                        }
                    }
                    prev = seg;
                }
                if (element.options.closed) {
                    page.close();
                }
            }
        }

        function drawPath(element, page, pdf) {
            _drawPath(element, page, pdf);
            maybeFillStroke(element, page, pdf);
        }

        function drawMultiPath(element, page, pdf) {
            var paths = element.paths;
            for (var i = 0; i < paths.length; ++i) {
                _drawPath(paths[i], page, pdf);
            }
            maybeFillStroke(element, page, pdf);
        }

        function drawCircle(element, page, pdf) {
            var g = element.geometry();
            page.circle(g.center.x, g.center.y, g.radius);
            maybeFillStroke(element, page, pdf);
        }

        function drawArc(element, page, pdf) {
            var points = element.geometry().curvePoints();
            page.moveTo(points[0].x, points[0].y);
            for (var i = 1; i < points.length; ) {
                page.bezier(
                points[i].x, points[i++].y,
                points[i].x, points[i++].y,
                points[i].x, points[i++].y
            );
            }
            maybeFillStroke(element, page, pdf);
        }

        function drawText(element, page, pdf) {
            var style = PDF().parseFontDef(element.options.font);
            var pos = element._position;
            var mode;
            if (element.fill() && element.stroke()) {
                mode = PDF().TEXT_RENDERING_MODE.fillAndStroke;
            } else if (element.fill()) {
                mode = PDF().TEXT_RENDERING_MODE.fill;
            } else if (element.stroke()) {
                mode = PDF().TEXT_RENDERING_MODE.stroke;
            }

            page.transform(1, 0, 0, -1, pos.x, pos.y + style.fontSize);
            page.beginText();
            page.setFont(PDF().getFontURL(style), style.fontSize);
            page.setTextRenderingMode(mode);
            page.showText(element.content());
            page.endText();
        }

        function drawGroup(element, page, pdf) {
            var children = element.children;
            for (var i = 0; i < children.length; ++i) {
                drawElement(children[i], page, pdf);
            }
        }

        function drawImage(element, page, pdf) {
            var url = element.src();
            var rect = element.rect();
            var tl = rect.getOrigin();
            var sz = rect.getSize();
            page.transform(sz.width, 0, 0, -sz.height, tl.x, tl.y + sz.height);
            page.drawImage(url);
        }

        function exportPDF(group, options) {
            var defer = $.Deferred();

            group.options.set("pdf", options);
            drawing.pdf.toDataURL(group, defer.resolve);

            return defer.promise();
        }

        function parseColor(x) {
            var color = kendo.parseColor(x, true);
            return color ? color.toRGB() : null;
        }

        function optimize(root) {
            var clipbox = false;
            var matrix = geo.Matrix.unit();
            var currentBox = null;
            var changed;
            do {
                changed = false;
                root = opt(root);
            } while (root && changed);
            return { root: root, bbox: currentBox };

            function change(newShape) {
                changed = true;
                return newShape;
            }

            function visible(shape) {
                return (shape.visible() && shape.opacity() > 0 &&
                    (shouldDraw(shape.fill()) ||
                      shouldDraw(shape.stroke())));
            }

            function optArray(a) {
                var b = [];
                for (var i = 0; i < a.length; ++i) {
                    var el = opt(a[i]);
                    if (el != null) {
                        b.push(el);
                    }
                }
                return b;
            }

            function withClipping(shape, f) {
                var saveclipbox = clipbox;
                var savematrix = matrix;

                if (shape.transform()) {
                    matrix = matrix.multiplyCopy(shape.transform().matrix());
                }

                var clip = shape.clip();
                if (clip) {
                    clip = clip.bbox();
                    if (clip) {
                        clip = clip.bbox(matrix);
                        clipbox = clipbox ? geo.Rect.intersect(clipbox, clip) : clip;
                    }
                }

                try {
                    return f();
                }
                finally {
                    clipbox = saveclipbox;
                    matrix = savematrix;
                }
            }

            function inClipbox(shape) {
                if (clipbox == null) {
                    return false;
                }
                var box = shape.rawBBox().bbox(matrix);
                if (clipbox && box) {
                    box = geo.Rect.intersect(box, clipbox);
                }
                return box;
            }

            function opt(shape) {
                if (!(shape instanceof drawing.Group || shape instanceof drawing.MultiPath)) {
                    var box = inClipbox(shape);
                    if (!box) {
                        return change(null);
                    }
                    currentBox = currentBox ? geo.Rect.union(currentBox, box) : box;
                }
                return dispatch({
                    Path: function (shape) {
                        if (shape.segments.length === 0 || !visible(shape)) {
                            return change(null);
                        }
                        return shape;
                    },
                    MultiPath: function (shape) {
                        if (!visible(shape)) {
                            return change(null);
                        }
                        var el = new drawing.MultiPath(shape.options);
                        el.paths = optArray(shape.paths);
                        if (el.paths.length === 0) {
                            return change(null);
                        }
                        return el;
                    },
                    Circle: function (shape) {
                        if (!visible(shape)) {
                            return change(null);
                        }
                        return shape;
                    },
                    Arc: function (shape) {
                        if (!visible(shape)) {
                            return change(null);
                        }
                        return shape;
                    },
                    Text: function (shape) {
                        if (!/\S/.test(shape.content()) || !visible(shape)) {
                            return change(null);
                        }
                        return shape;
                    },
                    Image: function (shape) {
                        if (!(shape.visible() && shape.opacity() > 0)) {
                            return change(null);
                        }
                        return shape;
                    },
                    Group: function (shape) {
                        return withClipping(shape, function () {
                            var el = new drawing.Group(shape.options);
                            el.children = optArray(shape.children);
                            if (shape !== root && el.children.length === 0) {
                                return change(null);
                            }
                            return el;
                        });
                    }
                }, shape);
            }
        }

        kendo.deepExtend(drawing, {
            exportPDF: exportPDF,

            pdf: {
                toDataURL: toDataURL,
                toBlob: toBlob,
                saveAs: saveAs,
                toStream: render
            }
        });

    })(window.kendo, window.kendo.jQuery);

    (function ($, parseFloat, Math) {

        "use strict";

        /* jshint eqnull:true */

        /* -----[ local vars ]----- */

        var drawing = kendo.drawing;
        var geo = kendo.geometry;
        var slice = Array.prototype.slice;
        var browser = kendo.support.browser;

        var IMAGE_CACHE = {};

        /* -----[ exports ]----- */

        drawing.drawDOM = function (element) {
            var defer = $.Deferred();
            element = $(element)[0];

            if (element) {
                cacheImages(element, function () {
                    var group = new drawing.Group();

                    // translate to start of page
                    var pos = element.getBoundingClientRect();
                    setTransform(group, [1, 0, 0, 1, -pos.left, -pos.top]);

                    nodeInfo._clipbox = false;
                    nodeInfo._matrix = geo.Matrix.unit();
                    nodeInfo._stackingContext = {
                        element: element,
                        group: group
                    };

                    renderElement(element, group);
                    defer.resolve(group);
                });
            } else {
                defer.reject("No element to export");
            }

            return defer.promise();
        };

        var nodeInfo = {};

        var parseGradient = (function () {
            var tok_linear_gradient = /^((-webkit-|-moz-|-o-|-ms-)?linear-gradient\s*)\(/;
            var tok_radial_gradient = /^((-webkit-|-moz-|-o-|-ms-)?radial-gradient\s*)\(/;
            var tok_percent = /^([-0-9.]+%)/;
            var tok_length = /^([-0-9.]+px)/;
            var tok_keyword = /^(left|right|top|bottom|to|center)\W/;
            var tok_angle = /^([-0-9.]+(deg|grad|rad|turn))/;
            var tok_whitespace = /^(\s+)/;
            var tok_popen = /^(\()/;
            var tok_pclose = /^(\))/;
            var tok_comma = /^(,)/;

            return function (input) {
                var orig = input;
                function skip_ws() {
                    var m = tok_whitespace.exec(input);
                    if (m) {
                        input = input.substr(m[1].length);
                    }
                }
                function read(token) {
                    skip_ws();
                    var m = token.exec(input);
                    if (m) {
                        input = input.substr(m[1].length);
                        return m[1];
                    }
                }

                function read_stop() {
                    // XXX: do NOT use the parseColor (defined later) here.
                    // kendo.parseColor leaves the `match` data in the
                    // object, that would be lost after .toRGB().
                    var color = kendo.parseColor(input, true);
                    var length, percent;
                    if (color) {
                        input = input.substr(color.match[0].length);
                        color = color.toRGB();
                        if (!(length = read(tok_length))) {
                            percent = read(tok_percent);
                        }
                        return { color: color, length: length, percent: percent };
                    }
                }

                function read_linear_gradient(propName) {
                    var angle;
                    var to1, to2;
                    var stops = [];
                    var reverse = false;

                    if (read(tok_popen)) {
                        // 1. [ <angle> || to <side-or-corner>, ]?
                        angle = read(tok_angle);
                        if (angle) {
                            angle = parseAngle(angle);
                            read(tok_comma);
                        }
                        else {
                            to1 = read(tok_keyword);
                            if (to1 == "to") {
                                to1 = read(tok_keyword);
                            } else if (to1 && /^-/.test(propName)) {
                                reverse = true;
                            }
                            to2 = read(tok_keyword);
                            read(tok_comma);
                        }

                        if (/-moz-/.test(propName) && angle == null && to1 == null) {
                            var x = read(tok_percent), y = read(tok_percent);
                            reverse = true;
                            if (x == "0%") {
                                to1 = "left";
                            } else if (x == "100%") {
                                to1 = "right";
                            }
                            if (y == "0%") {
                                to2 = "top";
                            } else if (y == "100%") {
                                to2 = "bottom";
                            }
                            read(tok_comma);
                        }

                        // 2. color stops
                        while (input && !read(tok_pclose)) {
                            var stop = read_stop();
                            if (!stop) {
                                break;
                            }
                            stops.push(stop);
                            read(tok_comma);
                        }

                        return {
                            type: "linear",
                            angle: angle,
                            to: to1 && to2 ? to1 + " " + to2 : to1 ? to1 : to2 ? to2 : null,
                            stops: stops,
                            reverse: reverse,
                            orig: orig
                        };
                    }
                }

                var tok = read(tok_linear_gradient);
                if (tok) {
                    return read_linear_gradient(tok);
                }
            };
        })();

        // only function definitions after this line.
        return;

        function splitOnComma(input) {
            var ret = [];
            var last = 0, pos = 0;
            var in_paren = 0;
            var in_string = false;
            var m;

            function looking_at(rx) {
                return (m = rx.exec(input.substr(pos)));
            }

            function trim(str) {
                return str.replace(/^\s+|\s+$/g, "");
            }

            while (pos < input.length) {
                if (looking_at(/^\s+/)) {
                    pos += m[0].length;
                }
                else if (looking_at(/^[\(\[\{]/)) {
                    in_paren++;
                    pos++;
                }
                else if (looking_at(/^[\)\]\}]/)) {
                    in_paren--;
                    pos++;
                }
                else if (!in_string && looking_at(/^[\"\']/)) {
                    in_string = m[0];
                    pos++;
                }
                else if (in_string == "'" && looking_at(/^\\\'/)) {
                    pos += 2;
                }
                else if (in_string == '"' && looking_at(/^\\\"/)) {
                    pos += 2;
                }
                else if (in_string == "'" && looking_at(/^\'/)) {
                    in_string = false;
                    pos++;
                }
                else if (in_string == '"' && looking_at(/^\"/)) {
                    in_string = false;
                    pos++;
                }
                else if (looking_at(/^\s*,\s*/)) {
                    if (!in_string && !in_paren && pos > last) {
                        ret.push(trim(input.substring(last, pos)));
                        last = pos + m[0].length;
                    }
                    pos += m[0].length;
                }
                else {
                    pos++;
                }
            }
            if (last < pos) {
                ret.push(trim(input.substring(last, pos)));
            }
            return ret;
        }

        function parseColor(str, css) {
            var color = kendo.parseColor(str);
            if (color) {
                color = color.toRGB();
                if (css) {
                    color = color.toCssRgba();
                } else if (color.a === 0) {
                    color = null;
                }
            }
            return color;
        }

        function cacheImages(element, callback) {
            var urls = [];
            function add(url) {
                if (!IMAGE_CACHE[url]) {
                    IMAGE_CACHE[url] = true;
                    urls.push(url);
                }
            }
            (function dive(element) {
                var bg = backgroundImageURL(getPropertyValue(getComputedStyle(element), "background-image"));
                if (/^img$/i.test(element.tagName)) {
                    add(element.src);
                }
                if (bg) {
                    add(bg);
                }
                for (var i = element.firstChild; i; i = i.nextSibling) {
                    if (i.nodeType == 1) {
                        dive(i);
                    }
                }
            })(element);
            var count = urls.length;
            function next() {
                if (--count <= 0) {
                    callback();
                }
            }
            if (count === 0) {
                next();
            }
            urls.forEach(function (url) {
                var img = IMAGE_CACHE[url] = new Image();
                img.onload = next;
                img.onerror = function () {
                    IMAGE_CACHE[url] = null;
                    next();
                };
                img.src = url;
                // hack from https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
                // make sure the load event fires for cached images too
                if (img.complete || img.complete === undefined) {
                    img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
                    img.src = url;
                }
            });
        }

        function backgroundImageURL(backgroundImage) {
            var m = /^\s*url\((['"]?)(.*?)\1\)\s*$/i.exec(backgroundImage);
            if (m) {
                return m[2];
            }
        }

        function pushNodeInfo(element, style, group) {
            nodeInfo = Object.create(nodeInfo);
            nodeInfo[element.tagName.toLowerCase()] = {
                element: element,
                style: style
            };
            var decoration = getPropertyValue(style, "text-decoration");
            if (decoration && decoration != "none") {
                var color = getPropertyValue(style, "color");
                decoration.split(/\s+/g).forEach(function (name) {
                    if (!nodeInfo[name]) {
                        nodeInfo[name] = color;
                    }
                });
            }

            if (createsStackingContext(element)) {
                nodeInfo._stackingContext = {
                    element: element,
                    group: group
                };
            }
        }

        function popNodeInfo() {
            nodeInfo = Object.getPrototypeOf(nodeInfo);
        }

        function updateClipbox(path) {
            if (nodeInfo._clipbox != null) {
                var box = path.bbox(nodeInfo._matrix);
                if (nodeInfo._clipbox) {
                    nodeInfo._clipbox = geo.Rect.intersect(nodeInfo._clipbox, box);
                } else {
                    nodeInfo._clipbox = box;
                }
            }
        }

        function createsStackingContext(element) {
            var style = getComputedStyle(element);
            function prop(name) { return getPropertyValue(style, name); }
            if (prop("transform") != "none" ||
            (prop("position") != "static" && prop("z-index") != "auto") ||
            (prop("opacity") < 1)) {
                return true;
            }
        }

        function getComputedStyle(element) {
            return window.getComputedStyle(element);
        }

        function getPropertyValue(style, prop) {
            return style.getPropertyValue(prop) ||
            (browser.webkit && style.getPropertyValue("-webkit-" + prop)) ||
            (browser.firefox && style.getPropertyValue("-moz-" + prop)) ||
            (browser.opera && style.getPropertyValue("-o-" + prop)) ||
            (browser.msie && style.getPropertyValue("-ms-" + prop))
        ;
        }

        function pleaseSetPropertyValue(style, prop, value, important) {
            style.setProperty(prop, value, important);
            if (browser.webkit) {
                style.setProperty("-webkit-" + prop, value, important);
            } else if (browser.firefox) {
                style.setProperty("-moz-" + prop, value, important);
            } else if (browser.opera) {
                style.setProperty("-o-" + prop, value, important);
            } else if (browser.msie) {
                style.setProperty("-ms-" + prop, value, important);
                prop = "ms" + prop.replace(/(^|-)([a-z])/g, function (s, p1, p2) {
                    return p1 + p2.toUpperCase();
                });
                style[prop] = value;
            }
        }

        function getBorder(style, side) {
            side = "border-" + side;
            return {
                width: parseFloat(getPropertyValue(style, side + "-width")),
                style: getPropertyValue(style, side + "-style"),
                color: parseColor(getPropertyValue(style, side + "-color"), true)
            };
        }

        function saveStyle(element, func) {
            var prev = element.style.cssText;
            var result = func();
            element.style.cssText = prev;
            return result;
        }

        function getBorderRadius(style, side) {
            var r = getPropertyValue(style, "border-" + side + "-radius").split(/\s+/g).map(parseFloat);
            if (r.length == 1) {
                r.push(r[0]);
            }
            return sanitizeRadius({ x: r[0], y: r[1] });
        }

        function getContentBox(element) {
            var box = element.getBoundingClientRect();
            box = innerBox(box, "border-*-width", element);
            box = innerBox(box, "padding-*", element);
            return box;
        }

        function innerBox(box, prop, element) {
            var style, wt, wr, wb, wl;
            if (typeof prop == "string") {
                style = getComputedStyle(element);
                wt = parseFloat(getPropertyValue(style, prop.replace("*", "top")));
                wr = parseFloat(getPropertyValue(style, prop.replace("*", "right")));
                wb = parseFloat(getPropertyValue(style, prop.replace("*", "bottom")));
                wl = parseFloat(getPropertyValue(style, prop.replace("*", "left")));
            }
            else if (typeof prop == "number") {
                wt = wr = wb = wl = prop;
            }
            return {
                top: box.top + wt,
                right: box.right - wr,
                bottom: box.bottom - wb,
                left: box.left + wl,
                width: box.right - box.left - wr - wl,
                height: box.bottom - box.top - wb - wt
            };
        }

        function getTransform(style) {
            var transform = getPropertyValue(style, "transform");
            if (transform == "none") {
                return null;
            }
            var matrix = /^\s*matrix\(\s*(.*?)\s*\)\s*$/.exec(transform);
            if (matrix) {
                var origin = getPropertyValue(style, "transform-origin");
                matrix = matrix[1].split(/\s*,\s*/g).map(parseFloat);
                origin = origin.split(/\s+/g).map(parseFloat);
                return {
                    matrix: matrix,
                    origin: origin
                };
            }
        }

        function radiansToDegrees(radians) {
            return ((180 * radians) / Math.PI) % 360;
        }

        function parseAngle(angle) {
            var num = parseFloat(angle);
            if (/grad$/.test(angle)) {
                return Math.PI * num / 200;
            }
            else if (/rad$/.test(angle)) {
                return num;
            }
            else if (/turn$/.test(angle)) {
                return Math.PI * num * 2;
            }
            else if (/deg$/.test(angle)) {
                return Math.PI * num / 180;
            }
        }

        function setTransform(shape, m) {
            m = new geo.Matrix(m[0], m[1], m[2], m[3], m[4], m[5]);
            shape.transform(m);
            return m;
        }

        function setClipping(shape, clipPath) {
            shape.clip(clipPath);
        }

        function addArcToPath(path, x, y, options) {
            var points = new geo.Arc([x, y], options).curvePoints(), i = 1;
            while (i < points.length) {
                path.curveTo(points[i++], points[i++], points[i++]);
            }
        }

        function sanitizeRadius(r) {
            if (r.x <= 0 || r.y <= 0) {
                r.x = r.y = 0;
            }
            return r;
        }

        function elementRoundBox(element, box, type) {
            var style = getComputedStyle(element);

            var rTL = getBorderRadius(style, "top-left");
            var rTR = getBorderRadius(style, "top-right");
            var rBL = getBorderRadius(style, "bottom-left");
            var rBR = getBorderRadius(style, "bottom-right");

            if (type == "padding" || type == "content") {
                var bt = getBorder(style, "top");
                var br = getBorder(style, "right");
                var bb = getBorder(style, "bottom");
                var bl = getBorder(style, "left");
                rTL.x -= bl.width; rTL.y -= bt.width;
                rTR.x -= br.width; rTR.y -= bt.width;
                rBR.x -= br.width; rBR.y -= bb.width;
                rBL.x -= bl.width; rBL.y -= bb.width;
                if (type == "content") {
                    var pt = parseFloat(getPropertyValue(style, "padding-top"));
                    var pr = parseFloat(getPropertyValue(style, "padding-right"));
                    var pb = parseFloat(getPropertyValue(style, "padding-bottom"));
                    var pl = parseFloat(getPropertyValue(style, "padding-left"));
                    rTL.x -= pl; rTL.y -= pt;
                    rTR.x -= pr; rTR.y -= pt;
                    rBR.x -= pr; rBR.y -= pb;
                    rBL.x -= pl; rBL.y -= pb;
                }
            }

            if (typeof type == "number") {
                rTL.x -= type; rTL.y -= type;
                rTR.x -= type; rTR.y -= type;
                rBR.x -= type; rBR.y -= type;
                rBL.x -= type; rBL.y -= type;
            }

            return roundBox(box, rTL, rTR, rBR, rBL);
        }

        // Create a drawing.Path for a rounded rectangle.  Receives the
        // bounding box and the border-radiuses in CSS order (top-left,
        // top-right, bottom-right, bottom-left).  The radiuses must be
        // objects containing x (horiz. radius) and y (vertical radius).
        function roundBox(box, rTL, rTR, rBR, rBL) {
            var path = new drawing.Path({ fill: null, stroke: null });
            sanitizeRadius(rTL);
            sanitizeRadius(rTR);
            sanitizeRadius(rBR);
            sanitizeRadius(rBL);
            path.moveTo(box.left, box.top + rTL.y);
            if (rTL.x) {
                addArcToPath(path, box.left + rTL.x, box.top + rTL.y, {
                    startAngle: -180,
                    endAngle: -90,
                    radiusX: rTL.x,
                    radiusY: rTL.y
                });
            }
            path.lineTo(box.right - rTR.x, box.top);
            if (rTR.x) {
                addArcToPath(path, box.right - rTR.x, box.top + rTR.y, {
                    startAngle: -90,
                    endAngle: 0,
                    radiusX: rTR.x,
                    radiusY: rTR.y
                });
            }
            path.lineTo(box.right, box.bottom - rBR.y);
            if (rBR.x) {
                addArcToPath(path, box.right - rBR.x, box.bottom - rBR.y, {
                    startAngle: 0,
                    endAngle: 90,
                    radiusX: rBR.x,
                    radiusY: rBR.y
                });
            }
            path.lineTo(box.left + rBL.x, box.bottom);
            if (rBL.x) {
                addArcToPath(path, box.left + rBL.x, box.bottom - rBL.y, {
                    startAngle: 90,
                    endAngle: 180,
                    radiusX: rBL.x,
                    radiusY: rBL.y
                });
            }
            return path.close();
        }

        function _renderElement(element, group) {
            var style = getComputedStyle(element);

            var top = getBorder(style, "top");
            var right = getBorder(style, "right");
            var bottom = getBorder(style, "bottom");
            var left = getBorder(style, "left");

            var rTL = getBorderRadius(style, "top-left");
            var rTR = getBorderRadius(style, "top-right");
            var rBL = getBorderRadius(style, "bottom-left");
            var rBR = getBorderRadius(style, "bottom-right");

            var dir = getPropertyValue(style, "direction");

            var backgroundColor = getPropertyValue(style, "background-color");
            backgroundColor = parseColor(backgroundColor);

            var backgroundImage = splitOnComma(getPropertyValue(style, "background-image"));
            var backgroundRepeat = splitOnComma(getPropertyValue(style, "background-repeat"));
            var backgroundPosition = splitOnComma(getPropertyValue(style, "background-position"));
            var backgroundOrigin = splitOnComma(getPropertyValue(style, "background-origin"));
            var backgroundSize = splitOnComma(getPropertyValue(style, "background-size"));

            if (browser.msie && browser.version < 10) {
                // IE9 hacks.  getPropertyValue won't return the correct
                // value.  Sucks that we have to do it here, I'd prefer to
                // move it in getPropertyValue, but we don't have the
                // element.
                backgroundPosition = splitOnComma(element.currentStyle.backgroundPosition);
            }

            var innerbox = innerBox(element.getBoundingClientRect(), "border-*-width", element);

            // CSS "clip" property - if present, replace the group with a
            // new one which is clipped.  This must happen before drawing
            // the borders and background.
            (function () {
                var clip = getPropertyValue(style, "clip");
                var m = /^\s*rect\((.*)\)\s*$/.exec(clip);
                if (m) {
                    var a = m[1].split(/[ ,]+/g);
                    var top = a[0] == "auto" ? innerbox.top : parseFloat(a[0]) + innerbox.top;
                    var right = a[1] == "auto" ? innerbox.right : parseFloat(a[1]) + innerbox.left;
                    var bottom = a[2] == "auto" ? innerbox.bottom : parseFloat(a[2]) + innerbox.top;
                    var left = a[3] == "auto" ? innerbox.left : parseFloat(a[3]) + innerbox.left;
                    var tmp = new drawing.Group();
                    var clipPath = new drawing.Path()
                    .moveTo(left, top)
                    .lineTo(right, top)
                    .lineTo(right, bottom)
                    .lineTo(left, bottom)
                    .close();
                    setClipping(tmp, clipPath);
                    group.append(tmp);
                    group = tmp;
                    updateClipbox(clipPath);
                }
            })();

            var boxes = element.getClientRects();
            if (boxes.length == 1) {
                // Workaround the missing borders in Chrome!  getClientRects() boxes contains values
                // rounded to integer.  getBoundingClientRect() appears to work fine.  We still need
                // getClientRects() to support cases where there are more boxes (continued inline
                // elements that might have border/background).
                boxes = [element.getBoundingClientRect()];
            }

            // This function workarounds another Chrome bug, where boxes returned for a table with
            // border-collapse: collapse will overlap the table border.  Our rendering is not perfect in
            // such case anyway, but with this is better than without it.
            boxes = adjustBoxes(boxes);

            for (var i = 0; i < boxes.length; ++i) {
                drawOneBox(boxes[i], i === 0, i == boxes.length - 1);
            }

            // overflow: hidden/auto - if present, replace the group with
            // a new one clipped by the inner box.
            (function () {
                function clipit() {
                    var clipPath = elementRoundBox(element, innerbox, "padding");
                    var tmp = new drawing.Group();
                    setClipping(tmp, clipPath);
                    group.append(tmp);
                    group = tmp;
                    updateClipbox(clipPath);
                }
                if (/^(hidden|auto|scroll)/.test(getPropertyValue(style, "overflow"))) {
                    clipit();
                } else if (/^(hidden|auto|scroll)/.test(getPropertyValue(style, "overflow-x"))) {
                    clipit();
                } else if (/^(hidden|auto|scroll)/.test(getPropertyValue(style, "overflow-y"))) {
                    clipit();
                }
            })();

            if (!maybeRenderWidget(element, group)) {
                renderContents(element, group);
            }

            return group; // only utility functions after this line.

            function adjustBoxes(boxes) {
                if (/^td$/i.test(element.tagName)) {
                    var table = nodeInfo.table;
                    if (table && getPropertyValue(table.style, "border-collapse") == "collapse") {
                        var tableBorderLeft = getBorder(table.style, "left").width;
                        var tableBorderTop = getBorder(table.style, "top").width;
                        // check if we need to adjust
                        if (tableBorderLeft === 0 && tableBorderTop === 0) {
                            return boxes; // nope
                        }
                        var tableBox = table.element.getBoundingClientRect();
                        var firstCell = table.element.rows[0].cells[0];
                        var firstCellBox = firstCell.getBoundingClientRect();
                        if (firstCellBox.top == tableBox.top || firstCellBox.left == tableBox.left) {
                            return slice.call(boxes).map(function (box) {
                                return {
                                    left: box.left + tableBorderLeft,
                                    top: box.top + tableBorderTop,
                                    right: box.right + tableBorderLeft,
                                    bottom: box.bottom + tableBorderTop,
                                    height: box.height,
                                    width: box.width
                                };
                            });
                        }
                    }
                }
                return boxes;
            }

            // this function will be called to draw each border.  it
            // draws starting at origin and the resulted path must be
            // translated/rotated to be placed in the proper position.
            //
            // arguments are named as if it draws the top border:
            //
            //    - `len` the length of the edge
            //    - `Wtop` the width of the edge (i.e. border-top-width)
            //    - `Wleft` the width of the left edge (border-left-width)
            //    - `Wright` the width of the right edge
            //    - `rl` and `rl` -- the border radius on the left and right
            //      (objects containing x and y, for horiz/vertical radius)
            //    - `transform` -- transformation to apply
            //
            function drawEdge(color, len, Wtop, Wleft, Wright, rl, rr, transform) {
                if (Wtop <= 0) {
                    return;
                }

                var path, edge = new drawing.Group();
                setTransform(edge, transform);
                group.append(edge);

                sanitizeRadius(rl);
                sanitizeRadius(rr);

                // draw main border.  this is the area without the rounded corners
                path = new drawing.Path({
                    fill: { color: color },
                    stroke: null
                });
                edge.append(path);
                path.moveTo(rl.x ? Math.max(rl.x, Wleft) : 0, 0)
                .lineTo(len - (rr.x ? Math.max(rr.x, Wright) : 0), 0)
                .lineTo(len - Math.max(rr.x, Wright), Wtop)
                .lineTo(Math.max(rl.x, Wleft), Wtop)
                .close();

                if (rl.x) {
                    drawRoundCorner(Wleft, rl, [-1, 0, 0, 1, rl.x, 0]);
                }

                if (rr.x) {
                    drawRoundCorner(Wright, rr, [1, 0, 0, 1, len - rr.x, 0]);
                }

                // draws one round corner, starting at origin (needs to be
                // translated/rotated to be placed properly).
                function drawRoundCorner(Wright, r, transform) {
                    var angle = Math.PI / 2 * Wright / (Wright + Wtop);

                    // not sanitizing this one, because negative values
                    // are useful to fill the box correctly.
                    var ri = {
                        x: r.x - Wright,
                        y: r.y - Wtop
                    };

                    var path = new drawing.Path({
                        fill: { color: color },
                        stroke: null
                    }).moveTo(0, 0);

                    setTransform(path, transform);

                    addArcToPath(path, 0, r.y, {
                        startAngle: -90,
                        endAngle: -radiansToDegrees(angle),
                        radiusX: r.x,
                        radiusY: r.y
                    });

                    if (ri.x > 0 && ri.y > 0) {
                        path.lineTo(ri.x * Math.cos(angle), r.y - ri.y * Math.sin(angle));
                        addArcToPath(path, 0, r.y, {
                            startAngle: -radiansToDegrees(angle),
                            endAngle: -90,
                            radiusX: ri.x,
                            radiusY: ri.y,
                            anticlockwise: true
                        });
                    }
                    else if (ri.x > 0) {
                        path.lineTo(ri.x, Wtop)
                        .lineTo(0, Wtop);
                    }
                    else {
                        path.lineTo(ri.x, Wtop)
                        .lineTo(ri.x, 0);
                    }

                    edge.append(path.close());
                }
            }

            function drawBackground(box) {
                var background = new drawing.Group();
                setClipping(background, roundBox(box, rTL, rTR, rBR, rBL));
                group.append(background);

                if (backgroundColor) {
                    var path = new drawing.Path({
                        fill: { color: backgroundColor.toCssRgba() },
                        stroke: null
                    });
                    path.moveTo(box.left, box.top)
                    .lineTo(box.right, box.top)
                    .lineTo(box.right, box.bottom)
                    .lineTo(box.left, box.bottom)
                    .close();
                    background.append(path);
                }

                var bgImage, bgRepeat, bgPosition, bgOrigin, bgSize;

                for (var i = backgroundImage.length; --i >= 0; ) {
                    bgImage = backgroundImage[i];
                    bgRepeat = backgroundRepeat[i] || backgroundRepeat[backgroundRepeat.length - 1];
                    bgPosition = backgroundPosition[i] || backgroundPosition[backgroundPosition.length - 1];
                    bgOrigin = backgroundOrigin[i] || backgroundOrigin[backgroundOrigin.length - 1];
                    bgSize = backgroundSize[i] || backgroundSize[backgroundSize.length - 1];
                    drawOneBackground(background, box, bgImage, bgRepeat, bgPosition, bgOrigin, bgSize);
                }
            }

            function drawOneBackground(group, box, backgroundImage, backgroundRepeat, backgroundPosition, backgroundOrigin, backgroundSize) {
                if (!backgroundImage || (backgroundImage == "none")) {
                    return;
                }

                // SVG taints the canvas, can't draw it.
                if (/^url\(\"data:image\/svg/i.test(backgroundImage)) {
                    return;
                }

                var url = backgroundImageURL(backgroundImage);
                if (url) {
                    var img = IMAGE_CACHE[url];
                    if (img && img.width > 0 && img.height > 0) {
                        drawBackgroundImage(group, box, img.width, img.height, function (group, rect) {
                            group.append(new drawing.Image(url, rect));
                        });
                    }
                }
                else {
                    var gradient = parseGradient(backgroundImage);
                    if (gradient) {
                        drawBackgroundImage(group, box, box.width, box.height, gradientRenderer(gradient));
                    }
                }

                function drawBackgroundImage(group, box, img_width, img_height, renderBG) {
                    var aspect_ratio = img_width / img_height;

                    // for background-origin: border-box the box is already appropriate
                    var orgBox = box;
                    if (backgroundOrigin == "content-box") {
                        orgBox = innerBox(orgBox, "border-*-width", element);
                        orgBox = innerBox(orgBox, "padding-*", element);
                    } else if (backgroundOrigin == "padding-box") {
                        orgBox = innerBox(orgBox, "border-*-width", element);
                    }

                    if (!/^\s*auto(\s+auto)?\s*$/.test(backgroundSize)) {
                        var size = backgroundSize.split(/\s+/g);
                        // compute width
                        if (/%$/.test(size[0])) {
                            img_width = orgBox.width * parseFloat(size[0]) / 100;
                        } else {
                            img_width = parseFloat(size[0]);
                        }
                        // compute height
                        if (size.length == 1 || size[1] == "auto") {
                            img_height = img_width / aspect_ratio;
                        } else if (/%$/.test(size[1])) {
                            img_height = orgBox.height * parseFloat(size[1]) / 100;
                        } else {
                            img_height = parseFloat(size[1]);
                        }
                    }

                    var pos = (backgroundPosition + "").split(/\s+/);
                    if (pos.length == 1) {
                        pos[1] = "50%";
                    }

                    if (/%$/.test(pos[0])) {
                        pos[0] = parseFloat(pos[0]) / 100 * (orgBox.width - img_width);
                    } else {
                        pos[0] = parseFloat(pos[0]);
                    }
                    if (/%$/.test(pos[1])) {
                        pos[1] = parseFloat(pos[1]) / 100 * (orgBox.height - img_height);
                    } else {
                        pos[1] = parseFloat(pos[1]);
                    }

                    var rect = new geo.Rect([orgBox.left + pos[0], orgBox.top + pos[1]], [img_width, img_height]);

                    // XXX: background-repeat could be implemented more
                    //      efficiently as a fill pattern (at least for PDF
                    //      output, probably SVG too).

                    function rewX() {
                        while (rect.origin.x > box.left) {
                            rect.origin.x -= img_width;
                        }
                    }

                    function rewY() {
                        while (rect.origin.y > box.top) {
                            rect.origin.y -= img_height;
                        }
                    }

                    function repeatX() {
                        while (rect.origin.x < box.right) {
                            renderBG(group, rect.clone());
                            rect.origin.x += img_width;
                        }
                    }

                    if (backgroundRepeat == "no-repeat") {
                        renderBG(group, rect);
                    }
                    else if (backgroundRepeat == "repeat-x") {
                        rewX();
                        repeatX();
                    }
                    else if (backgroundRepeat == "repeat-y") {
                        rewY();
                        while (rect.origin.y < box.bottom) {
                            renderBG(group, rect.clone());
                            rect.origin.y += img_height;
                        }
                    }
                    else if (backgroundRepeat == "repeat") {
                        rewX();
                        rewY();
                        var origin = rect.origin.clone();
                        while (rect.origin.y < box.bottom) {
                            rect.origin.x = origin.x;
                            repeatX();
                            rect.origin.y += img_height;
                        }
                    }
                }
            }

            // draws a single border box
            function drawOneBox(box, isFirst, isLast) {
                if (box.width === 0 || box.height === 0) {
                    return;
                }

                drawBackground(box);

                var shouldDrawLeft = (left.width > 0 && ((isFirst && dir == "ltr") || (isLast && dir == "rtl")));
                var shouldDrawRight = (right.width > 0 && ((isLast && dir == "ltr") || (isFirst && dir == "rtl")));

                // The most general case is that the 4 borders have different widths and border
                // radiuses.  The way that is handled is by drawing 3 Paths for each border: the
                // straight line, and two round corners which represent half of the entire rounded
                // corner.  To simplify code those shapes are drawed at origin (by the drawEdge
                // function), then translated/rotated into the right position.
                //
                // However, this leads to poor results due to rounding in the simpler cases where
                // borders are straight lines.  Therefore we handle a few such cases separately with
                // straight lines. C^wC^wC^w -- nope, scratch that.  poor rendering was because of a bug
                // in Chrome (getClientRects() returns rounded integer values rather than exact floats.
                // web dev is still a ghetto.)

                // first, just in case there is no border...
                if (top.width === 0 && left.width === 0 && right.width === 0 && bottom.width === 0) {
                    return;
                }

                if (true) { // so that it's easy to comment out..  uglifyjs will drop the spurious if.

                    // if all borders have equal colors...
                    if (top.color == right.color && top.color == bottom.color && top.color == left.color) {

                        // if same widths too, we can draw the whole border by stroking a single path.
                        if (top.width == right.width && top.width == bottom.width && top.width == left.width) {
                            if (shouldDrawLeft && shouldDrawRight) {
                                // reduce box by half the border width, so we can draw it by stroking.
                                box = innerBox(box, top.width / 2);

                                // adjust the border radiuses, again by top.width/2, and make the path element.
                                var path = elementRoundBox(element, box, top.width / 2);
                                path.options.stroke = {
                                    color: top.color,
                                    width: top.width
                                };
                                group.append(path);
                                return;
                            }
                        }
                    }

                    // if border radiuses are zero and widths are at most one pixel, we can again use simple
                    // paths.
                    if (rTL.x === 0 && rTR.x === 0 && rBR.x === 0 && rBL.x === 0) {
                        // alright, 1.9px will do as well.  the difference in color blending should not be
                        // noticeable.
                        if (top.width < 2 && left.width < 2 && right.width < 2 && bottom.width < 2) {
                            // top border
                            if (top.width > 0) {
                                group.append(
                                new drawing.Path({
                                    stroke: { width: top.width, color: top.color }
                                })
                                    .moveTo(box.left, box.top + top.width / 2)
                                    .lineTo(box.right, box.top + top.width / 2)
                            );
                            }

                            // bottom border
                            if (bottom.width > 0) {
                                group.append(
                                new drawing.Path({
                                    stroke: { width: bottom.width, color: bottom.color }
                                })
                                    .moveTo(box.left, box.bottom - bottom.width / 2)
                                    .lineTo(box.right, box.bottom - bottom.width / 2)
                            );
                            }

                            // left border
                            if (shouldDrawLeft) {
                                group.append(
                                new drawing.Path({
                                    stroke: { width: left.width, color: left.color }
                                })
                                    .moveTo(box.left + left.width / 2, box.top)
                                    .lineTo(box.left + left.width / 2, box.bottom)
                            );
                            }

                            // right border
                            if (shouldDrawRight) {
                                group.append(
                                new drawing.Path({
                                    stroke: { width: right.width, color: right.color }
                                })
                                    .moveTo(box.right - right.width / 2, box.top)
                                    .lineTo(box.right - right.width / 2, box.bottom)
                            );
                            }

                            return;
                        }
                    }

                }

                // top border
                drawEdge(top.color,
                     box.width, top.width, left.width, right.width,
                     rTL, rTR,
                     [1, 0, 0, 1, box.left, box.top]);

                // bottom border
                drawEdge(bottom.color,
                     box.width, bottom.width, right.width, left.width,
                     rBR, rBL,
                     [-1, 0, 0, -1, box.right, box.bottom]);

                // for left/right borders we need to invert the border-radiuses
                function inv(p) {
                    return { x: p.y, y: p.x };
                }

                // left border
                drawEdge(left.color,
                     box.height, left.width, bottom.width, top.width,
                     inv(rBL), inv(rTL),
                     [0, -1, 1, 0, box.left, box.bottom]);

                // right border
                drawEdge(right.color,
                     box.height, right.width, top.width, bottom.width,
                     inv(rTR), inv(rBR),
                     [0, 1, -1, 0, box.right, box.top]);
            }
        }

        function gradientRenderer(gradient) {
            return function (group, rect) {
                var width = rect.width(), height = rect.height(), tl = rect.topLeft();

                switch (gradient.type) {
                    case "linear":

                        // figure out the angle.
                        var angle = gradient.angle != null ? gradient.angle : Math.PI;
                        switch (gradient.to) {
                            case "top":
                                angle = 0;
                                break;
                            case "left":
                                angle = -Math.PI / 2;
                                break;
                            case "bottom":
                                angle = Math.PI;
                                break;
                            case "right":
                                angle = Math.PI / 2;
                                break;
                            case "top left": case "left top":
                                angle = -Math.atan2(height, width);
                                break;
                            case "top right": case "right top":
                                angle = Math.atan2(height, width);
                                break;
                            case "bottom left": case "left bottom":
                                angle = Math.PI + Math.atan2(height, width);
                                break;
                            case "bottom right": case "right bottom":
                                angle = Math.PI - Math.atan2(height, width);
                                break;
                        }

                        if (gradient.reverse) {
                            angle -= Math.PI;
                        }

                        // limit the angle between 0..2π
                        angle %= 2 * Math.PI;
                        if (angle < 0) {
                            angle += 2 * Math.PI;
                        }

                        // compute gradient's start/end points.  here len is the length of the gradient line
                        // and x,y is the end point relative to the center of the rectangle in conventional
                        // (math) axis direction.

                        // this is the original (unscaled) length of the gradient line.  needed to deal with
                        // absolutely positioned color stops.  formula from the CSS spec:
                        // http://dev.w3.org/csswg/css-images-3/#linear-gradient-syntax
                        var pxlen = Math.abs(width * Math.sin(angle)) + Math.abs(height * Math.cos(angle));

                        // The math below is pretty simple, but it took a while to figure out.  We compute x
                        // and y, the *end* of the gradient line.  However, we want to transform them into
                        // element-based coordinates (SVG's gradientUnits="objectBoundingBox").  That means,
                        // x=0 is the left edge, x=1 is the right edge, y=0 is the top edge and y=1 is the
                        // bottom edge.
                        //
                        // A naive approach would use the original angle for these calculations.  Say we'd
                        // like to draw a gradient angled at 45° in a 100x400 box.  When we use
                        // objectBoundingBox, the renderer will draw it in a 1x1 *square* box, and then
                        // scale that to the desired dimensions.  The 45° angle will look more like 70°
                        // after scaling.  SVG (http://www.w3.org/TR/SVG/pservers.html#LinearGradients) says
                        // the following:
                        //
                        //     When gradientUnits="objectBoundingBox" and ‘gradientTransform’ is the
                        //     identity matrix, the normal of the linear gradient is perpendicular to the
                        //     gradient vector in object bounding box space (i.e., the abstract coordinate
                        //     system where (0,0) is at the top/left of the object bounding box and (1,1) is
                        //     at the bottom/right of the object bounding box). When the object's bounding
                        //     box is not square, the gradient normal which is initially perpendicular to
                        //     the gradient vector within object bounding box space may render
                        //     non-perpendicular relative to the gradient vector in user space. If the
                        //     gradient vector is parallel to one of the axes of the bounding box, the
                        //     gradient normal will remain perpendicular. This transformation is due to
                        //     application of the non-uniform scaling transformation from bounding box space
                        //     to user space.
                        //
                        // which is an extremely long and confusing way to tell what I just said above.
                        //
                        // For this reason we need to apply the reverse scaling to the original angle, so
                        // that when it'll finally be rendered it'll actually be at the desired slope.  Now
                        // I'll let you figure out the math yourself.

                        var scaledAngle = Math.atan(width * Math.tan(angle) / height);
                        var sin = Math.sin(scaledAngle), cos = Math.cos(scaledAngle);
                        var len = Math.abs(sin) + Math.abs(cos);
                        var x = len / 2 * sin;
                        var y = len / 2 * cos;

                        // Because of the arctangent, our scaledAngle ends up between -π/2..π/2, possibly
                        // losing the intended direction of the gradient.  The following fixes it.
                        if (angle > Math.PI / 2 && angle <= 3 * Math.PI / 2) {
                            x = -x;
                            y = -y;
                        }

                        // compute the color stops.
                        var implicit = [], right = 0;
                        var stops = gradient.stops.map(function (s, i) {
                            var offset = s.percent;
                            if (offset) {
                                offset = parseFloat(offset) / 100;
                            } else if (s.length) {
                                offset = parseFloat(s.length) / pxlen;
                            } else if (i === 0) {
                                offset = 0;
                            } else if (i == gradient.stops.length - 1) {
                                offset = 1;
                            }
                            var stop = {
                                color: s.color.toCssRgba(),
                                offset: offset
                            };
                            if (offset != null) {
                                right = offset;
                                // fix implicit offsets
                                implicit.forEach(function (s, i) {
                                    var stop = s.stop;
                                    stop.offset = s.left + (right - s.left) * (i + 1) / (implicit.length + 1);
                                });
                                implicit = [];
                            } else {
                                implicit.push({ left: right, stop: stop });
                            }
                            return stop;
                        });

                        var start = [0.5 - x, 0.5 + y];
                        var end = [0.5 + x, 0.5 - y];

                        // finally, draw it.
                        group.append(
                    drawing.Path.fromRect(rect)
                        .stroke(null)
                        .fill(new drawing.LinearGradient({
                            start: start,
                            end: end,
                            stops: stops,
                            userSpace: false
                        }))
                );
                        break;
                    case "radial":
                        // XXX:
                        if (window.console && window.console.log) {
                            window.console.log("Radial gradients are not yet supported in HTML renderer");
                        }
                        break;
                }
            };
        }

        function maybeRenderWidget(element, group) {
            if (element.getAttribute(kendo.attr("role"))) {
                var widget = kendo.widgetInstance($(element));
                if (widget && (widget.exportDOMVisual || widget.exportVisual)) {
                    var visual;
                    if (widget.exportDOMVisual) {
                        visual = widget.exportDOMVisual();
                    } else {
                        visual = widget.exportVisual();
                    }

                    var wrap = new drawing.Group();
                    wrap.children.push(visual);

                    var bbox = element.getBoundingClientRect();
                    wrap.transform(geo.transform().translate(bbox.left, bbox.top));

                    group.append(wrap);

                    return true;
                }
            }
        }

        function renderImage(element, url, group) {
            var box = getContentBox(element);
            var rect = new geo.Rect([box.left, box.top], [box.width, box.height]);
            var image = new drawing.Image(url, rect);
            setClipping(image, elementRoundBox(element, box, "content"));
            group.append(image);
        }

        function zIndexSort(a, b) {
            var sa = getComputedStyle(a);
            var sb = getComputedStyle(b);
            var za = parseFloat(getPropertyValue(sa, "z-index"));
            var zb = parseFloat(getPropertyValue(sb, "z-index"));
            var pa = getPropertyValue(sa, "position");
            var pb = getPropertyValue(sb, "position");
            if (isNaN(za) && isNaN(zb)) {
                if ((/static|absolute/.test(pa)) && (/static|absolute/.test(pb))) {
                    return 0;
                }
                if (pa == "static") {
                    return -1;
                }
                if (pb == "static") {
                    return 1;
                }
                return 0;
            }
            if (isNaN(za)) {
                return zb === 0 ? 0 : zb > 0 ? -1 : 1;
            }
            if (isNaN(zb)) {
                return za === 0 ? 0 : za > 0 ? 1 : -1;
            }
            return parseFloat(za) - parseFloat(zb);
        }

        function renderContents(element, group) {
            switch (element.tagName.toLowerCase()) {
                case "img":
                    renderImage(element, element.src, group);
                    break;

                case "canvas":
                    try {
                        renderImage(element, element.toDataURL("image/jpeg"), group);
                    } catch (ex) {
                        // tainted; can't draw it, ignore.
                    }
                    break;

                case "textarea":
                case "input":
                    break;

                default:
                    var blocks = [], floats = [], inline = [], positioned = [];
                    for (var i = element.firstChild; i; i = i.nextSibling) {
                        switch (i.nodeType) {
                            case 3:         // Text
                                if (/\S/.test(i.data)) {
                                    renderText(element, i, group);
                                }
                                break;
                            case 1:         // Element
                                var style = getComputedStyle(i);
                                var display = getPropertyValue(style, "display");
                                var floating = getPropertyValue(style, "float");
                                var position = getPropertyValue(style, "position");
                                if (position != "static") {
                                    positioned.push(i);
                                }
                                else if (display != "inline") {
                                    if (floating != "none") {
                                        floats.push(i);
                                    } else {
                                        blocks.push(i);
                                    }
                                }
                                else {
                                    inline.push(i);
                                }
                                break;
                        }
                    }

                    blocks.sort(zIndexSort).forEach(function (el) { renderElement(el, group); });
                    floats.sort(zIndexSort).forEach(function (el) { renderElement(el, group); });
                    inline.sort(zIndexSort).forEach(function (el) { renderElement(el, group); });
                    positioned.sort(zIndexSort).forEach(function (el) { renderElement(el, group); });
            }
        }

        function renderText(element, node, group) {
            var style = getComputedStyle(element);

            if (parseFloat(getPropertyValue(style, "text-indent")) < -500) {
                // assume it should not be displayed.  the slider's
                // draggable handle displays a Drag text for some reason,
                // having text-indent: -3333px.
                return;
            }

            var text = node.data;
            var range = element.ownerDocument.createRange();
            var align = getPropertyValue(style, "text-align");
            var isJustified = align == "justify";

            // skip whitespace
            var start = 0;
            var end = /\S\s*$/.exec(node.data).index + 1;

            function doChunk() {
                while (!/\S/.test(text.charAt(start))) {
                    if (start >= end) {
                        return true;
                    }
                    start++;
                }
                range.setStart(node, start);
                var len = 0;
                while (++start <= end) {
                    ++len;
                    range.setEnd(node, start);

                    // for justified text we must split at each space, as
                    // space has variable width.  otherwise we can
                    // optimize and split only at end of line (i.e. when a
                    // new rectangle would be created).
                    if (len > 1 && ((isJustified && /\s/.test(text.charAt(start - 1))) || range.getClientRects().length > 1)) {
                        //
                        // In IE, getClientRects for a <li> element will return an additional rectangle for the bullet, but
                        // *only* when only the first char in the LI is selected.  Checking if len > 1 above appears to be a
                        // good workaround.
                        //
                        //// DEBUG
                        // Array.prototype.slice.call(range.getClientRects()).concat([ range.getBoundingClientRect() ]).forEach(function(r){
                        //     $("<div>").css({
                        //         position  : "absolute",
                        //         left      : r.left + "px",
                        //         top       : r.top + "px",
                        //         width     : r.right - r.left + "px",
                        //         height    : r.bottom - r.top + "px",
                        //         boxSizing : "border-box",
                        //         border    : "1px solid red"
                        //     }).appendTo(document.body);
                        // });
                        range.setEnd(node, --start);
                        break;
                    }
                }

                // another workaround for IE: if we rely on getBoundingClientRect() we'll overlap with the bullet for LI
                // elements.  Calling getClientRects() and using the *first* rect appears to give us the correct location.
                var box = range.getClientRects()[0];

                var str = range.toString().replace(/\s+$/, "");
                drawText(str, box);
            }

            var fontSize = getPropertyValue(style, "font-size");
            var lineHeight = getPropertyValue(style, "line-height");

            // simply getPropertyValue("font") doesn't work in Firefox :-\
            var font = [
            getPropertyValue(style, "font-style"),
            getPropertyValue(style, "font-variant"),
            getPropertyValue(style, "font-weight"),
            fontSize, // no need for line height here; it breaks layout in FF
            getPropertyValue(style, "font-family")
        ].join(" ");

            fontSize = parseFloat(fontSize);
            lineHeight = parseFloat(lineHeight);

            if (fontSize === 0) {
                return;
            }

            var color = getPropertyValue(style, "color");

            function drawText(str, box) {
                str = str.replace(/[\r\n ]+/g, " ");

                // In IE the box height will be approximately lineHeight, while in
                // other browsers it'll (correctly) be the height of the bounding
                // box for the current text/font.  Which is to say, IE sucks again.
                // The only good solution I can think of is to measure the text
                // ourselves and center the bounding box.
                if (browser.msie && !isNaN(lineHeight)) {
                    var size = drawing.util.measureText(str, { font: font });
                    var top = (box.top + box.bottom - size.height) / 2;
                    box = {
                        top: top,
                        right: box.right,
                        bottom: top + size.height,
                        left: box.left,
                        height: size.height,
                        width: box.right - box.left
                    };
                }

                // var path = new drawing.Path({ stroke: { color: "red" }});
                // path.moveTo(box.left, box.top)
                //     .lineTo(box.right, box.top)
                //     .lineTo(box.right, box.bottom)
                //     .lineTo(box.left, box.bottom)
                //     .close();
                // group.append(path);

                var text = new drawing.Text(str, new geo.Point(box.left, box.top), {
                    font: font,
                    fill: { color: color }
                });
                group.append(text);
                decorate(box);
            }

            function decorate(box) {
                /*jshint -W069 */// aaaaargh!  JSHate.
                line(nodeInfo["underline"], box.bottom);
                line(nodeInfo["line-through"], box.bottom - box.height / 2.7);
                line(nodeInfo["overline"], box.top);
                function line(color, ypos) {
                    if (color) {
                        var width = fontSize / 12;
                        var path = new drawing.Path({ stroke: {
                            width: width,
                            color: color
                        }
                        });

                        ypos -= width;
                        path.moveTo(box.left, ypos)
                        .lineTo(box.right, ypos);
                        group.append(path);
                    }
                }
            }

            while (!doChunk()) { }
        }

        function groupInStackingContext(group, zIndex) {
            var main = nodeInfo._stackingContext.group;
            var a = main.children;
            for (var i = 0; i < a.length; ++i) {
                if (a[i]._dom_zIndex != null && a[i]._dom_zIndex > zIndex) {
                    break;
                }
            }

            var tmp = new drawing.Group();
            main.insertAt(tmp, i);
            tmp._dom_zIndex = zIndex;

            // if (nodeInfo._matrix) {
            //     tmp.transform(nodeInfo._matrix);
            // }
            if (nodeInfo._clipbox) {
                tmp.clip(drawing.Path.fromRect(nodeInfo._clipbox));
            }

            return tmp;
        }

        function renderElement(element, container) {
            if (/^(style|script|link|meta|iframe|svg)$/i.test(element.tagName)) {
                return;
            }

            if (nodeInfo._clipbox == null) {
                return;
            }

            var style = getComputedStyle(element);
            var opacity = parseFloat(getPropertyValue(style, "opacity"));
            var visibility = getPropertyValue(style, "visibility");
            var display = getPropertyValue(style, "display");

            if (opacity === 0 || visibility == "hidden" || display == "none") {
                return;
            }

            var tr = getTransform(style);
            var group;

            var zIndex = getPropertyValue(style, "z-index");
            if ((tr || opacity < 1) && zIndex == "auto") {
                zIndex = 0;
            }
            if (zIndex != "auto") {
                group = groupInStackingContext(container, zIndex);
            } else {
                group = new drawing.Group();
                container.append(group);
            }

            // XXX: remove at some point
            group.DEBUG = $(element).data("debug");

            if (opacity < 1) {
                group.opacity(opacity * group.opacity());
            }

            pushNodeInfo(element, style, group);

            if (!tr) {
                _renderElement(element, group);
            }
            else {
                saveStyle(element, function () {
                    // must clear transform, so getBoundingClientRect returns correct values.
                    pleaseSetPropertyValue(element.style, "transform", "none", "important");

                    // must also clear transitions, so correct values are returned *immediately*
                    pleaseSetPropertyValue(element.style, "transition", "none", "important");

                    // the presence of any transform makes it behave like it had position: relative,
                    // because why not.
                    // http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/
                    if (getPropertyValue(style, "position") == "static") {
                        // but only if it's not already positioned. :-/
                        pleaseSetPropertyValue(element.style, "position", "relative", "important");
                    }

                    // must translate to origin before applying the CSS
                    // transformation, then translate back.
                    var bbox = element.getBoundingClientRect();
                    var x = bbox.left + tr.origin[0];
                    var y = bbox.top + tr.origin[1];
                    var m = [1, 0, 0, 1, -x, -y];
                    m = mmul(m, tr.matrix);
                    m = mmul(m, [1, 0, 0, 1, x, y]);
                    m = setTransform(group, m);

                    nodeInfo._matrix = nodeInfo._matrix.multiplyCopy(m);

                    _renderElement(element, group);
                });
            }

            popNodeInfo();

            //drawDebugBox(element, container);
        }

        function drawDebugBox(element, group) {
            var box = element.getBoundingClientRect();
            var path = drawing.Path.fromRect(new geo.Rect([box.left, box.top], [box.width, box.height]));
            group.append(path);
        }

        function mmul(a, b) {
            var a1 = a[0], b1 = a[1], c1 = a[2], d1 = a[3], e1 = a[4], f1 = a[5];
            var a2 = b[0], b2 = b[1], c2 = b[2], d2 = b[3], e2 = b[4], f2 = b[5];
            return [
            a1 * a2 + b1 * c2, a1 * b2 + b1 * d2,
            c1 * a2 + d1 * c2, c1 * b2 + d1 * d2,
            e1 * a2 + f1 * c2 + e2, e1 * b2 + f1 * d2 + f2
        ];
        }

    })(window.kendo.jQuery, parseFloat, Math);

    (function ($, Math) {
        // Imports ================================================================
        var doc = document,
        noop = $.noop,

        kendo = window.kendo,
        Class = kendo.Class,
        util = kendo.util,

        animationFrame = kendo.animationFrame,
        deepExtend = kendo.deepExtend;

        // Base element animation ================================================
        var Animation = Class.extend({
            init: function (element, options) {
                var anim = this;

                anim.options = deepExtend({}, anim.options, options);
                anim.element = element;
            },

            options: {
                duration: 500,
                easing: "swing"
            },

            setup: noop,
            step: noop,

            play: function () {
                var anim = this,
                options = anim.options,
                easing = $.easing[options.easing],
                duration = options.duration,
                delay = options.delay || 0,
                start = util.now() + delay,
                finish = start + duration;

                if (duration === 0) {
                    anim.step(1);
                    anim.abort();
                } else {
                    setTimeout(function () {
                        var loop = function () {
                            if (anim._stopped) {
                                return;
                            }

                            var wallTime = util.now();

                            var time = util.limitValue(wallTime - start, 0, duration);
                            var pos = time / duration;
                            var easingPos = easing(pos, time, 0, 1, duration);

                            anim.step(easingPos);

                            if (wallTime < finish) {
                                animationFrame(loop);
                            } else {
                                anim.abort();
                            }
                        };

                        loop();
                    }, delay);
                }
            },

            abort: function () {
                this._stopped = true;
            },

            destroy: function () {
                this.abort();
            }
        });

        // Animation factory =====================================================
        var AnimationFactory = function () {
            this._items = [];
        };

        AnimationFactory.prototype = {
            register: function (name, type) {
                this._items.push({
                    name: name,
                    type: type
                });
            },

            create: function (element, options) {
                var items = this._items;
                var match;

                if (options && options.type) {
                    var type = options.type.toLowerCase();
                    for (var i = 0; i < items.length; i++) {
                        if (items[i].name.toLowerCase() === type) {
                            match = items[i];
                            break;
                        }
                    }
                }

                if (match) {
                    return new match.type(element, options);
                }
            }
        };

        AnimationFactory.current = new AnimationFactory();

        Animation.create = function (type, element, options) {
            return AnimationFactory.current.create(type, element, options);
        };

        // Exports ================================================================
        deepExtend(kendo.drawing, {
            Animation: Animation,
            AnimationFactory: AnimationFactory
        });

    })(window.kendo.jQuery, Math);





    /* jshint eqnull: true */
    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        NS = ".kendoValidator",
        INVALIDMSG = "k-invalid-msg",
        invalidMsgRegExp = new RegExp(INVALIDMSG, 'i'),
        INVALIDINPUT = "k-invalid",
        emailRegExp = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,
        urlRegExp = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i,
        INPUTSELECTOR = ":input:not(:button,[type=submit],[type=reset],[disabled],[readonly])",
        CHECKBOXSELECTOR = ":checkbox:not([disabled],[readonly])",
        NUMBERINPUTSELECTOR = "[type=number],[type=range]",
        BLUR = "blur",
        NAME = "name",
        FORM = "form",
        NOVALIDATE = "novalidate",
        proxy = $.proxy,
        patternMatcher = function (value, pattern) {
            if (typeof pattern === "string") {
                pattern = new RegExp('^(?:' + pattern + ')$');
            }
            return pattern.test(value);
        },
        matcher = function (input, selector, pattern) {
            var value = input.val();

            if (input.filter(selector).length && value !== "") {
                return patternMatcher(value, pattern);
            }
            return true;
        },
        hasAttribute = function (input, name) {
            if (input.length) {
                return input[0].attributes[name] != null;
            }
            return false;
        };

        if (!kendo.ui.validator) {
            kendo.ui.validator = { rules: {}, messages: {} };
        }

        function resolveRules(element) {
            var resolvers = kendo.ui.validator.ruleResolvers || {},
            rules = {},
            name;

            for (name in resolvers) {
                $.extend(true, rules, resolvers[name].resolve(element));
            }
            return rules;
        }

        function decode(value) {
            return value.replace(/&amp/g, '&amp;')
            .replace(/&quot;/g, '"')
            .replace(/&#39;/g, "'")
            .replace(/&lt;/g, '<')
            .replace(/&gt;/g, '>');
        }

        function numberOfDecimalDigits(value) {
            value = (value + "").split('.');
            if (value.length > 1) {
                return value[1].length;
            }
            return 0;
        }

        function parseHtml(text) {
            if ($.parseHTML) {
                return $($.parseHTML(text));
            }
            return $(text);
        }

        function searchForMessageContainer(elements, fieldName) {
            var containers = $(),
            element,
            attr;

            for (var idx = 0, length = elements.length; idx < length; idx++) {
                element = elements[idx];
                if (invalidMsgRegExp.test(element.className)) {
                    attr = element.getAttribute(kendo.attr("for"));
                    if (attr === fieldName) {
                        containers = containers.add(element);
                    }
                }
            }
            return containers;
        }

        var Validator = Widget.extend({
            init: function (element, options) {
                var that = this,
                resolved = resolveRules(element),
                validateAttributeSelector = "[" + kendo.attr("validate") + "!=false]";

                options = options || {};

                options.rules = $.extend({}, kendo.ui.validator.rules, resolved.rules, options.rules);
                options.messages = $.extend({}, kendo.ui.validator.messages, resolved.messages, options.messages);

                Widget.fn.init.call(that, element, options);

                that._errorTemplate = kendo.template(that.options.errorTemplate);

                if (that.element.is(FORM)) {
                    that.element.attr(NOVALIDATE, NOVALIDATE);
                }

                that._inputSelector = INPUTSELECTOR + validateAttributeSelector;
                that._checkboxSelector = CHECKBOXSELECTOR + validateAttributeSelector;

                that._errors = {};
                that._attachEvents();
                that._isValidated = false;
            },

            events: ["validate", "change"],

            options: {
                name: "Validator",
                errorTemplate: '<span class="k-widget k-tooltip k-tooltip-validation">' +
                '<span class="k-icon k-warning"> </span> #=message#</span>',
                messages: {
                    required: "{0} is required",
                    pattern: "{0} is not valid",
                    min: "{0} should be greater than or equal to {1}",
                    max: "{0} should be smaller than or equal to {1}",
                    step: "{0} is not valid",
                    email: "{0} is not valid email",
                    url: "{0} is not valid URL",
                    date: "{0} is not valid date"
                },
                rules: {
                    required: function (input) {
                        var checkbox = input.filter("[type=checkbox]").length && !input.is(":checked"),
                        value = input.val();

                        return !(hasAttribute(input, "required") && (value === "" || !value || checkbox));
                    },
                    pattern: function (input) {
                        if (input.filter("[type=text],[type=email],[type=url],[type=tel],[type=search],[type=password]").filter("[pattern]").length && input.val() !== "") {
                            return patternMatcher(input.val(), input.attr("pattern"));
                        }
                        return true;
                    },
                    min: function (input) {
                        if (input.filter(NUMBERINPUTSELECTOR + ",[" + kendo.attr("type") + "=number]").filter("[min]").length && input.val() !== "") {
                            var min = parseFloat(input.attr("min")) || 0,
                            val = kendo.parseFloat(input.val());

                            return min <= val;
                        }
                        return true;
                    },
                    max: function (input) {
                        if (input.filter(NUMBERINPUTSELECTOR + ",[" + kendo.attr("type") + "=number]").filter("[max]").length && input.val() !== "") {
                            var max = parseFloat(input.attr("max")) || 0,
                            val = kendo.parseFloat(input.val());

                            return max >= val;
                        }
                        return true;
                    },
                    step: function (input) {
                        if (input.filter(NUMBERINPUTSELECTOR + ",[" + kendo.attr("type") + "=number]").filter("[step]").length && input.val() !== "") {
                            var min = parseFloat(input.attr("min")) || 0,
                            step = parseFloat(input.attr("step")) || 1,
                            val = parseFloat(input.val()),
                            decimals = numberOfDecimalDigits(step),
                            raise;

                            if (decimals) {
                                raise = Math.pow(10, decimals);
                                return (((val - min) * raise) % (step * raise)) / Math.pow(100, decimals) === 0;
                            }
                            return ((val - min) % step) === 0;
                        }
                        return true;
                    },
                    email: function (input) {
                        return matcher(input, "[type=email],[" + kendo.attr("type") + "=email]", emailRegExp);
                    },
                    url: function (input) {
                        return matcher(input, "[type=url],[" + kendo.attr("type") + "=url]", urlRegExp);
                    },
                    date: function (input) {
                        if (input.filter("[type^=date],[" + kendo.attr("type") + "=date]").length && input.val() !== "") {
                            return kendo.parseDate(input.val(), input.attr(kendo.attr("format"))) !== null;
                        }
                        return true;
                    }
                },
                validateOnBlur: true
            },

            destroy: function () {
                Widget.fn.destroy.call(this);

                this.element.off(NS);
            },

            value: function () {
                if (!this._isValidated) {
                    return false;
                }

                return this.errors().length === 0;
            },

            _submit: function (e) {
                if (!this.validate()) {
                    e.stopPropagation();
                    e.stopImmediatePropagation();
                    e.preventDefault();
                    return false;
                }
                return true;
            },

            _checkElement: function (element) {
                var state = this.value();

                this.validateInput(element);

                if (this.value() !== state) {
                    this.trigger("change");
                }
            },

            _attachEvents: function () {
                var that = this;

                if (that.element.is(FORM)) {
                    that.element.on("submit" + NS, proxy(that._submit, that));
                }

                if (that.options.validateOnBlur) {
                    if (!that.element.is(INPUTSELECTOR)) {
                        that.element.on(BLUR + NS, that._inputSelector, function () {
                            that._checkElement($(this));
                        });

                        that.element.on("click" + NS, that._checkboxSelector, function () {
                            that._checkElement($(this));
                        });
                    } else {
                        that.element.on(BLUR + NS, function () {
                            that._checkElement(that.element);
                        });

                        if (that.element.is(CHECKBOXSELECTOR)) {
                            that.element.on("click" + NS, function () {
                                that._checkElement(that.element);
                            });
                        }
                    }
                }
            },

            validate: function () {
                var inputs;
                var idx;
                var result = false;
                var length;

                var isValid = this.value();

                this._errors = {};

                if (!this.element.is(INPUTSELECTOR)) {
                    var invalid = false;

                    inputs = this.element.find(this._inputSelector);

                    for (idx = 0, length = inputs.length; idx < length; idx++) {
                        if (!this.validateInput(inputs.eq(idx))) {
                            invalid = true;
                        }
                    }

                    result = !invalid;
                } else {
                    result = this.validateInput(this.element);
                }

                this.trigger("validate", { valid: result });

                if (isValid !== result) {
                    this.trigger("change");
                }

                return result;
            },

            validateInput: function (input) {
                input = $(input);

                this._isValidated = true;

                var that = this,
                template = that._errorTemplate,
                result = that._checkValidity(input),
                valid = result.valid,
                className = "." + INVALIDMSG,
                fieldName = (input.attr(NAME) || ""),
                lbl = that._findMessageContainer(fieldName).add(input.next(className).filter(function () {
                    var element = $(this);
                    if (element.filter("[" + kendo.attr("for") + "]").length) {
                        return element.attr(kendo.attr("for")) === fieldName;
                    }

                    return true;

                })).hide(),
                messageText;

                input.removeAttr("aria-invalid");

                if (!valid) {
                    messageText = that._extractMessage(input, result.key);
                    that._errors[fieldName] = messageText;
                    var messageLabel = parseHtml(template({ message: decode(messageText) }));

                    that._decorateMessageContainer(messageLabel, fieldName);

                    if (!lbl.replaceWith(messageLabel).length) {
                        messageLabel.insertAfter(input);
                    }
                    messageLabel.show();

                    input.attr("aria-invalid", true);
                } else {
                    delete that._errors[fieldName];
                }

                input.toggleClass(INVALIDINPUT, !valid);

                return valid;
            },

            hideMessages: function () {
                var that = this,
                className = "." + INVALIDMSG,
                element = that.element;

                if (!element.is(INPUTSELECTOR)) {
                    element.find(className).hide();
                } else {
                    element.next(className).hide();
                }
            },

            _findMessageContainer: function (fieldName) {
                var locators = kendo.ui.validator.messageLocators,
                name,
                containers = $();

                for (var idx = 0, length = this.element.length; idx < length; idx++) {
                    containers = containers.add(searchForMessageContainer(this.element[idx].getElementsByTagName("*"), fieldName));
                }

                for (name in locators) {
                    containers = containers.add(locators[name].locate(this.element, fieldName));
                }

                return containers;
            },

            _decorateMessageContainer: function (container, fieldName) {
                var locators = kendo.ui.validator.messageLocators,
                name;

                container.addClass(INVALIDMSG)
                .attr(kendo.attr("for"), fieldName || "");

                for (name in locators) {
                    locators[name].decorate(container, fieldName);
                }

                container.attr("role", "alert");
            },

            _extractMessage: function (input, ruleKey) {
                var that = this,
                customMessage = that.options.messages[ruleKey],
                fieldName = input.attr(NAME);

                customMessage = kendo.isFunction(customMessage) ? customMessage(input) : customMessage;

                return kendo.format(input.attr(kendo.attr(ruleKey + "-msg")) || input.attr("validationMessage") || input.attr("title") || customMessage || "", fieldName, input.attr(ruleKey));
            },

            _checkValidity: function (input) {
                var rules = this.options.rules,
                rule;

                for (rule in rules) {
                    if (!rules[rule].call(this, input)) {
                        return { valid: false, key: rule };
                    }
                }

                return { valid: true };
            },

            errors: function () {
                var results = [],
                errors = this._errors,
                error;

                for (error in errors) {
                    results.push(errors[error]);
                }
                return results;
            }
        });

        kendo.ui.plugin(Validator);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        support = kendo.support,
        document = window.document,
        Class = kendo.Class,
        Observable = kendo.Observable,
        now = $.now,
        extend = $.extend,
        OS = support.mobileOS,
        invalidZeroEvents = OS && OS.android,
        DEFAULT_MIN_HOLD = 800,
        DEFAULT_THRESHOLD = support.browser.msie ? 5 : 0, // WP8 and W8 are very sensitive and always report move.

        // UserEvents events
        PRESS = "press",
        HOLD = "hold",
        SELECT = "select",
        START = "start",
        MOVE = "move",
        END = "end",
        CANCEL = "cancel",
        TAP = "tap",
        RELEASE = "release",
        GESTURESTART = "gesturestart",
        GESTURECHANGE = "gesturechange",
        GESTUREEND = "gestureend",
        GESTURETAP = "gesturetap";

        var THRESHOLD = {
            "api": 0,
            "touch": 0,
            "mouse": 9,
            "pointer": 9
        };

        function touchDelta(touch1, touch2) {
            var x1 = touch1.x.location,
            y1 = touch1.y.location,
            x2 = touch2.x.location,
            y2 = touch2.y.location,
            dx = x1 - x2,
            dy = y1 - y2;

            return {
                center: {
                    x: (x1 + x2) / 2,
                    y: (y1 + y2) / 2
                },

                distance: Math.sqrt(dx * dx + dy * dy)
            };
        }

        function getTouches(e) {
            var touches = [],
            originalEvent = e.originalEvent,
            currentTarget = e.currentTarget,
            idx = 0, length,
            changedTouches,
            touch;

            if (e.api) {
                touches.push({
                    id: 2,  // hardcoded ID for API call;
                    event: e,
                    target: e.target,
                    currentTarget: e.target,
                    location: e,
                    type: "api"
                });
            }
            else if (e.type.match(/touch/)) {
                changedTouches = originalEvent ? originalEvent.changedTouches : [];
                for (length = changedTouches.length; idx < length; idx++) {
                    touch = changedTouches[idx];
                    touches.push({
                        location: touch,
                        event: e,
                        target: touch.target,
                        currentTarget: currentTarget,
                        id: touch.identifier,
                        type: "touch"
                    });
                }
            }
            else if (support.pointers || support.msPointers) {
                touches.push({
                    location: originalEvent,
                    event: e,
                    target: e.target,
                    currentTarget: currentTarget,
                    id: originalEvent.pointerId,
                    type: "pointer"
                });
            } else {
                touches.push({
                    id: 1, // hardcoded ID for mouse event;
                    event: e,
                    target: e.target,
                    currentTarget: currentTarget,
                    location: e,
                    type: "mouse"
                });
            }

            return touches;
        }

        var TouchAxis = Class.extend({
            init: function (axis, location) {
                var that = this;

                that.axis = axis;

                that._updateLocationData(location);

                that.startLocation = that.location;
                that.velocity = that.delta = 0;
                that.timeStamp = now();
            },

            move: function (location) {
                var that = this,
                offset = location["page" + that.axis],
                timeStamp = now(),
                timeDelta = (timeStamp - that.timeStamp) || 1; // Firing manually events in tests can make this 0;

                if (!offset && invalidZeroEvents) {
                    return;
                }

                that.delta = offset - that.location;

                that._updateLocationData(location);

                that.initialDelta = offset - that.startLocation;
                that.velocity = that.delta / timeDelta;
                that.timeStamp = timeStamp;
            },

            _updateLocationData: function (location) {
                var that = this, axis = that.axis;

                that.location = location["page" + axis];
                that.client = location["client" + axis];
                that.screen = location["screen" + axis];
            }
        });

        var Touch = Class.extend({
            init: function (userEvents, target, touchInfo) {
                extend(this, {
                    x: new TouchAxis("X", touchInfo.location),
                    y: new TouchAxis("Y", touchInfo.location),
                    type: touchInfo.type,
                    threshold: userEvents.threshold || THRESHOLD[touchInfo.type],
                    userEvents: userEvents,
                    target: target,
                    currentTarget: touchInfo.currentTarget,
                    initialTouch: touchInfo.target,
                    id: touchInfo.id,
                    pressEvent: touchInfo,
                    _moved: false,
                    _finished: false
                });
            },

            press: function () {
                this._holdTimeout = setTimeout($.proxy(this, "_hold"), this.userEvents.minHold);
                this._trigger(PRESS, this.pressEvent);
            },

            _hold: function () {
                this._trigger(HOLD, this.pressEvent);
            },

            move: function (touchInfo) {
                var that = this;

                if (that._finished) { return; }

                that.x.move(touchInfo.location);
                that.y.move(touchInfo.location);

                if (!that._moved) {
                    if (that._withinIgnoreThreshold()) {
                        return;
                    }

                    if (!UserEvents.current || UserEvents.current === that.userEvents) {
                        that._start(touchInfo);
                    } else {
                        return that.dispose();
                    }
                }

                // Event handlers may cancel the drag in the START event handler, hence the double check for pressed.
                if (!that._finished) {
                    that._trigger(MOVE, touchInfo);
                }
            },

            end: function (touchInfo) {
                var that = this;

                that.endTime = now();

                if (that._finished) { return; }

                // Mark the object as finished if there are blocking operations in the event handlers (alert/confirm)
                that._finished = true;

                that._trigger(RELEASE, touchInfo); // Release should be fired before TAP (as click is after mouseup/touchend)

                if (that._moved) {
                    that._trigger(END, touchInfo);
                } else {
                    that._trigger(TAP, touchInfo);
                }

                clearTimeout(that._holdTimeout);

                that.dispose();
            },

            dispose: function () {
                var userEvents = this.userEvents,
                activeTouches = userEvents.touches;

                this._finished = true;
                this.pressEvent = null;
                clearTimeout(this._holdTimeout);

                activeTouches.splice($.inArray(this, activeTouches), 1);
            },

            skip: function () {
                this.dispose();
            },

            cancel: function () {
                this.dispose();
            },

            isMoved: function () {
                return this._moved;
            },

            _start: function (touchInfo) {
                clearTimeout(this._holdTimeout);

                this.startTime = now();
                this._moved = true;
                this._trigger(START, touchInfo);
            },

            _trigger: function (name, touchInfo) {
                var that = this,
                jQueryEvent = touchInfo.event,
                data = {
                    touch: that,
                    x: that.x,
                    y: that.y,
                    target: that.target,
                    event: jQueryEvent
                };

                if (that.userEvents.notify(name, data)) {
                    jQueryEvent.preventDefault();
                }
            },

            _withinIgnoreThreshold: function () {
                var xDelta = this.x.initialDelta,
                yDelta = this.y.initialDelta;

                return Math.sqrt(xDelta * xDelta + yDelta * yDelta) <= this.threshold;
            }
        });

        function withEachUpEvent(callback) {
            var downEvents = kendo.eventMap.up.split(" "),
            idx = 0,
            length = downEvents.length;

            for (; idx < length; idx++) {
                callback(downEvents[idx]);
            }
        }

        var UserEvents = Observable.extend({
            init: function (element, options) {
                var that = this,
                filter,
                ns = kendo.guid();

                options = options || {};
                filter = that.filter = options.filter;
                that.threshold = options.threshold || DEFAULT_THRESHOLD;
                that.minHold = options.minHold || DEFAULT_MIN_HOLD;
                that.touches = [];
                that._maxTouches = options.multiTouch ? 2 : 1;
                that.allowSelection = options.allowSelection;
                that.captureUpIfMoved = options.captureUpIfMoved;
                that.eventNS = ns;

                element = $(element).handler(that);
                Observable.fn.init.call(that);

                extend(that, {
                    element: element,
                    // the touch events lock to the element anyway, so no need for the global setting
                    surface: options.global && !support.touch ? $(document.documentElement) : $(options.surface || element),
                    stopPropagation: options.stopPropagation,
                    pressed: false
                });

                that.surface.handler(that)
                .on(kendo.applyEventMap("move", ns), "_move")
                .on(kendo.applyEventMap("up cancel", ns), "_end");

                element.on(kendo.applyEventMap("down", ns), filter, "_start");

                if (support.pointers || support.msPointers) {
                    element.css("-ms-touch-action", "pinch-zoom double-tap-zoom");
                }

                if (options.preventDragEvent) {
                    element.on(kendo.applyEventMap("dragstart", ns), kendo.preventDefault);
                }

                element.on(kendo.applyEventMap("mousedown", ns), filter, { root: element }, "_select");

                if (that.captureUpIfMoved && support.eventCapture) {
                    var surfaceElement = that.surface[0],
                    preventIfMovingProxy = $.proxy(that.preventIfMoving, that);

                    withEachUpEvent(function (eventName) {
                        surfaceElement.addEventListener(eventName, preventIfMovingProxy, true);
                    });
                }

                that.bind([
            PRESS,
            HOLD,
            TAP,
            START,
            MOVE,
            END,
            RELEASE,
            CANCEL,
            GESTURESTART,
            GESTURECHANGE,
            GESTUREEND,
            GESTURETAP,
            SELECT
            ], options);
            },

            preventIfMoving: function (e) {
                if (this._isMoved()) {
                    e.preventDefault();
                }
            },

            destroy: function () {
                var that = this;

                if (that._destroyed) {
                    return;
                }

                that._destroyed = true;

                if (that.captureUpIfMoved && support.eventCapture) {
                    var surfaceElement = that.surface[0];
                    withEachUpEvent(function (eventName) {
                        surfaceElement.removeEventListener(eventName, that.preventIfMoving);
                    });
                }

                that.element.kendoDestroy(that.eventNS);
                that.surface.kendoDestroy(that.eventNS);
                that.element.removeData("handler");
                that.surface.removeData("handler");
                that._disposeAll();

                that.unbind();
                delete that.surface;
                delete that.element;
                delete that.currentTarget;
            },

            capture: function () {
                UserEvents.current = this;
            },

            cancel: function () {
                this._disposeAll();
                this.trigger(CANCEL);
            },

            notify: function (eventName, data) {
                var that = this,
                touches = that.touches;

                if (this._isMultiTouch()) {
                    switch (eventName) {
                        case MOVE:
                            eventName = GESTURECHANGE;
                            break;
                        case END:
                            eventName = GESTUREEND;
                            break;
                        case TAP:
                            eventName = GESTURETAP;
                            break;
                    }

                    extend(data, { touches: touches }, touchDelta(touches[0], touches[1]));
                }

                return this.trigger(eventName, extend(data, { type: eventName }));
            },

            // API
            press: function (x, y, target) {
                this._apiCall("_start", x, y, target);
            },

            move: function (x, y) {
                this._apiCall("_move", x, y);
            },

            end: function (x, y) {
                this._apiCall("_end", x, y);
            },

            _isMultiTouch: function () {
                return this.touches.length > 1;
            },

            _maxTouchesReached: function () {
                return this.touches.length >= this._maxTouches;
            },

            _disposeAll: function () {
                var touches = this.touches;
                while (touches.length > 0) {
                    touches.pop().dispose();
                }
            },

            _isMoved: function () {
                return $.grep(this.touches, function (touch) {
                    return touch.isMoved();
                }).length;
            },

            _select: function (e) {
                if (!this.allowSelection || this.trigger(SELECT, { event: e })) {
                    e.preventDefault();
                }
            },

            _start: function (e) {
                var that = this,
                idx = 0,
                filter = that.filter,
                target,
                touches = getTouches(e),
                length = touches.length,
                touch,
                which = e.which;

                if ((which && which > 1) || (that._maxTouchesReached())) {
                    return;
                }

                UserEvents.current = null;

                that.currentTarget = e.currentTarget;

                if (that.stopPropagation) {
                    e.stopPropagation();
                }

                for (; idx < length; idx++) {
                    if (that._maxTouchesReached()) {
                        break;
                    }

                    touch = touches[idx];

                    if (filter) {
                        target = $(touch.currentTarget); // target.is(filter) ? target : target.closest(filter, that.element);
                    } else {
                        target = that.element;
                    }

                    if (!target.length) {
                        continue;
                    }

                    touch = new Touch(that, target, touch);
                    that.touches.push(touch);
                    touch.press();

                    if (that._isMultiTouch()) {
                        that.notify("gesturestart", {});
                    }
                }
            },

            _move: function (e) {
                this._eachTouch("move", e);
            },

            _end: function (e) {
                this._eachTouch("end", e);
            },

            _eachTouch: function (methodName, e) {
                var that = this,
                dict = {},
                touches = getTouches(e),
                activeTouches = that.touches,
                idx,
                touch,
                touchInfo,
                matchingTouch;

                for (idx = 0; idx < activeTouches.length; idx++) {
                    touch = activeTouches[idx];
                    dict[touch.id] = touch;
                }

                for (idx = 0; idx < touches.length; idx++) {
                    touchInfo = touches[idx];
                    matchingTouch = dict[touchInfo.id];

                    if (matchingTouch) {
                        matchingTouch[methodName](touchInfo);
                    }
                }
            },

            _apiCall: function (type, x, y, target) {
                this[type]({
                    api: true,
                    pageX: x,
                    pageY: y,
                    clientX: x,
                    clientY: y,
                    target: $(target || this.element)[0],
                    stopPropagation: $.noop,
                    preventDefault: $.noop
                });
            }
        });

        UserEvents.defaultThreshold = function (value) {
            DEFAULT_THRESHOLD = value;
        };

        UserEvents.minHold = function (value) {
            DEFAULT_MIN_HOLD = value;
        };

        kendo.getTouches = getTouches;
        kendo.touchDelta = touchDelta;
        kendo.UserEvents = UserEvents;
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        support = kendo.support,
        document = window.document,
        Class = kendo.Class,
        Widget = kendo.ui.Widget,
        Observable = kendo.Observable,
        UserEvents = kendo.UserEvents,
        proxy = $.proxy,
        extend = $.extend,
        getOffset = kendo.getOffset,
        draggables = {},
        dropTargets = {},
        dropAreas = {},
        lastDropTarget,
        elementUnderCursor = kendo.elementUnderCursor,
        KEYUP = "keyup",
        CHANGE = "change",

        // Draggable events
        DRAGSTART = "dragstart",
        HOLD = "hold",
        DRAG = "drag",
        DRAGEND = "dragend",
        DRAGCANCEL = "dragcancel",

        // DropTarget events
        DRAGENTER = "dragenter",
        DRAGLEAVE = "dragleave",
        DROP = "drop";

        function contains(parent, child) {
            try {
                return $.contains(parent, child) || parent == child;
            } catch (e) {
                return false;
            }
        }

        function numericCssPropery(element, property) {
            return parseInt(element.css(property), 10) || 0;
        }

        function within(value, range) {
            return Math.min(Math.max(value, range.min), range.max);
        }

        function containerBoundaries(container, element) {
            var offset = getOffset(container),
            minX = offset.left + numericCssPropery(container, "borderLeftWidth") + numericCssPropery(container, "paddingLeft"),
            minY = offset.top + numericCssPropery(container, "borderTopWidth") + numericCssPropery(container, "paddingTop"),
            maxX = minX + container.width() - element.outerWidth(true),
            maxY = minY + container.height() - element.outerHeight(true);

            return {
                x: { min: minX, max: maxX },
                y: { min: minY, max: maxY }
            };
        }

        function checkTarget(target, targets, areas) {
            var theTarget, theFilter, i = 0,
            targetLen = targets && targets.length,
            areaLen = areas && areas.length;

            while (target && target.parentNode) {
                for (i = 0; i < targetLen; i++) {
                    theTarget = targets[i];
                    if (theTarget.element[0] === target) {
                        return { target: theTarget, targetElement: target };
                    }
                }

                for (i = 0; i < areaLen; i++) {
                    theFilter = areas[i];
                    // -- MANUAL DEFECT FIX -- 
                    // DropTargetArea filter is not applied accordingly
                    // https://github.com/telerik/kendo-ui-core/issues/681
                    // if (support.matchesSelector.call(target, theFilter.options.filter)) {
                    if ($.contains(theFilter.element[0], target) && support.matchesSelector.call(target, theFilter.options.filter)) {
                        return { target: theFilter, targetElement: target };
                    }
                }

                target = target.parentNode;
            }

            return undefined;
        }

        var TapCapture = Observable.extend({
            init: function (element, options) {
                var that = this,
                domElement = element[0];

                that.capture = false;

                if (domElement.addEventListener) {
                    $.each(kendo.eventMap.down.split(" "), function () {
                        domElement.addEventListener(this, proxy(that._press, that), true);
                    });
                    $.each(kendo.eventMap.up.split(" "), function () {
                        domElement.addEventListener(this, proxy(that._release, that), true);
                    });
                } else {
                    $.each(kendo.eventMap.down.split(" "), function () {
                        domElement.attachEvent(this, proxy(that._press, that));
                    });
                    $.each(kendo.eventMap.up.split(" "), function () {
                        domElement.attachEvent(this, proxy(that._release, that));
                    });
                }

                Observable.fn.init.call(that);

                that.bind(["press", "release"], options || {});
            },

            captureNext: function () {
                this.capture = true;
            },

            cancelCapture: function () {
                this.capture = false;
            },

            _press: function (e) {
                var that = this;
                that.trigger("press");
                if (that.capture) {
                    e.preventDefault();
                }
            },

            _release: function (e) {
                var that = this;
                that.trigger("release");

                if (that.capture) {
                    e.preventDefault();
                    that.cancelCapture();
                }
            }
        });

        var PaneDimension = Observable.extend({
            init: function (options) {
                var that = this;
                Observable.fn.init.call(that);

                that.forcedEnabled = false;

                $.extend(that, options);

                that.scale = 1;

                if (that.horizontal) {
                    that.measure = "offsetWidth";
                    that.scrollSize = "scrollWidth";
                    that.axis = "x";
                } else {
                    that.measure = "offsetHeight";
                    that.scrollSize = "scrollHeight";
                    that.axis = "y";
                }
            },

            makeVirtual: function () {
                $.extend(this, {
                    virtual: true,
                    forcedEnabled: true,
                    _virtualMin: 0,
                    _virtualMax: 0
                });
            },

            virtualSize: function (min, max) {
                if (this._virtualMin !== min || this._virtualMax !== max) {
                    this._virtualMin = min;
                    this._virtualMax = max;
                    this.update();
                }
            },

            outOfBounds: function (offset) {
                return offset > this.max || offset < this.min;
            },

            forceEnabled: function () {
                this.forcedEnabled = true;
            },

            getSize: function () {
                return this.container[0][this.measure];
            },

            getTotal: function () {
                return this.element[0][this.scrollSize];
            },

            rescale: function (scale) {
                this.scale = scale;
            },

            update: function (silent) {
                var that = this,
                total = that.virtual ? that._virtualMax : that.getTotal(),
                scaledTotal = total * that.scale,
                size = that.getSize();

                if (total === 0) {
                    return; // we are not visible.
                }

                that.max = that.virtual ? -that._virtualMin : 0;
                that.size = size;
                that.total = scaledTotal;
                that.min = Math.min(that.max, size - scaledTotal);
                that.minScale = size / total;
                that.centerOffset = (scaledTotal - size) / 2;

                that.enabled = that.forcedEnabled || (scaledTotal > size);

                if (!silent) {
                    that.trigger(CHANGE, that);
                }
            }
        });

        var PaneDimensions = Observable.extend({
            init: function (options) {
                var that = this;

                Observable.fn.init.call(that);

                that.x = new PaneDimension(extend({ horizontal: true }, options));
                that.y = new PaneDimension(extend({ horizontal: false }, options));
                that.container = options.container;
                that.forcedMinScale = options.minScale;
                that.maxScale = options.maxScale || 100;

                that.bind(CHANGE, options);
            },

            rescale: function (newScale) {
                this.x.rescale(newScale);
                this.y.rescale(newScale);
                this.refresh();
            },

            centerCoordinates: function () {
                return { x: Math.min(0, -this.x.centerOffset), y: Math.min(0, -this.y.centerOffset) };
            },

            refresh: function () {
                var that = this;
                that.x.update();
                that.y.update();
                that.enabled = that.x.enabled || that.y.enabled;
                that.minScale = that.forcedMinScale || Math.min(that.x.minScale, that.y.minScale);
                that.fitScale = Math.max(that.x.minScale, that.y.minScale);
                that.trigger(CHANGE);
            }
        });

        var PaneAxis = Observable.extend({
            init: function (options) {
                var that = this;
                extend(that, options);
                Observable.fn.init.call(that);
            },

            outOfBounds: function () {
                return this.dimension.outOfBounds(this.movable[this.axis]);
            },

            dragMove: function (delta) {
                var that = this,
                dimension = that.dimension,
                axis = that.axis,
                movable = that.movable,
                position = movable[axis] + delta;

                if (!dimension.enabled) {
                    return;
                }

                if ((position < dimension.min && delta < 0) || (position > dimension.max && delta > 0)) {
                    delta *= that.resistance;
                }

                movable.translateAxis(axis, delta);
                that.trigger(CHANGE, that);
            }
        });

        var Pane = Class.extend({

            init: function (options) {
                var that = this,
                x,
                y,
                resistance,
                movable;

                extend(that, { elastic: true }, options);

                resistance = that.elastic ? 0.5 : 0;
                movable = that.movable;

                that.x = x = new PaneAxis({
                    axis: "x",
                    dimension: that.dimensions.x,
                    resistance: resistance,
                    movable: movable
                });

                that.y = y = new PaneAxis({
                    axis: "y",
                    dimension: that.dimensions.y,
                    resistance: resistance,
                    movable: movable
                });

                that.userEvents.bind(["move", "end", "gesturestart", "gesturechange"], {
                    gesturestart: function (e) {
                        that.gesture = e;
                        that.offset = that.dimensions.container.offset();
                    },

                    gesturechange: function (e) {
                        var previousGesture = that.gesture,
                        previousCenter = previousGesture.center,

                        center = e.center,

                        scaleDelta = e.distance / previousGesture.distance,

                        minScale = that.dimensions.minScale,
                        maxScale = that.dimensions.maxScale,
                        coordinates;

                        if (movable.scale <= minScale && scaleDelta < 1) {
                            // Resist shrinking. Instead of shrinking from 1 to 0.5, it will shrink to 0.5 + (1 /* minScale */ - 0.5) * 0.8 = 0.9;
                            scaleDelta += (1 - scaleDelta) * 0.8;
                        }

                        if (movable.scale * scaleDelta >= maxScale) {
                            scaleDelta = maxScale / movable.scale;
                        }

                        var offsetX = movable.x + that.offset.left,
                        offsetY = movable.y + that.offset.top;

                        coordinates = {
                            x: (offsetX - previousCenter.x) * scaleDelta + center.x - offsetX,
                            y: (offsetY - previousCenter.y) * scaleDelta + center.y - offsetY
                        };

                        movable.scaleWith(scaleDelta);

                        x.dragMove(coordinates.x);
                        y.dragMove(coordinates.y);

                        that.dimensions.rescale(movable.scale);
                        that.gesture = e;
                        e.preventDefault();
                    },

                    move: function (e) {
                        if (e.event.target.tagName.match(/textarea|input/i)) {
                            return;
                        }

                        if (x.dimension.enabled || y.dimension.enabled) {
                            x.dragMove(e.x.delta);
                            y.dragMove(e.y.delta);
                            e.preventDefault();
                        } else {
                            e.touch.skip();
                        }
                    },

                    end: function (e) {
                        e.preventDefault();
                    }
                });
            }
        });

        var TRANSFORM_STYLE = support.transitions.prefix + "Transform",
        translate;


        if (support.hasHW3D) {
            translate = function (x, y, scale) {
                return "translate3d(" + x + "px," + y + "px,0) scale(" + scale + ")";
            };
        } else {
            translate = function (x, y, scale) {
                return "translate(" + x + "px," + y + "px) scale(" + scale + ")";
            };
        }

        var Movable = Observable.extend({
            init: function (element) {
                var that = this;

                Observable.fn.init.call(that);

                that.element = $(element);
                that.element[0].style.webkitTransformOrigin = "left top";
                that.x = 0;
                that.y = 0;
                that.scale = 1;
                that._saveCoordinates(translate(that.x, that.y, that.scale));
            },

            translateAxis: function (axis, by) {
                this[axis] += by;
                this.refresh();
            },

            scaleTo: function (scale) {
                this.scale = scale;
                this.refresh();
            },

            scaleWith: function (scaleDelta) {
                this.scale *= scaleDelta;
                this.refresh();
            },

            translate: function (coordinates) {
                this.x += coordinates.x;
                this.y += coordinates.y;
                this.refresh();
            },

            moveAxis: function (axis, value) {
                this[axis] = value;
                this.refresh();
            },

            moveTo: function (coordinates) {
                extend(this, coordinates);
                this.refresh();
            },

            refresh: function () {
                var that = this,
                x = that.x,
                y = that.y,
                newCoordinates;

                if (that.round) {
                    x = Math.round(x);
                    y = Math.round(y);
                }

                newCoordinates = translate(x, y, that.scale);

                if (newCoordinates != that.coordinates) {
                    if (kendo.support.browser.msie && kendo.support.browser.version < 10) {
                        that.element[0].style.position = "absolute";
                        that.element[0].style.left = that.x + "px";
                        that.element[0].style.top = that.y + "px";

                    } else {
                        that.element[0].style[TRANSFORM_STYLE] = newCoordinates;
                    }
                    that._saveCoordinates(newCoordinates);
                    that.trigger(CHANGE);
                }
            },

            _saveCoordinates: function (coordinates) {
                this.coordinates = coordinates;
            }
        });

        var DropTarget = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                var group = that.options.group;

                if (!(group in dropTargets)) {
                    dropTargets[group] = [that];
                } else {
                    dropTargets[group].push(that);
                }
            },

            events: [
            DRAGENTER,
            DRAGLEAVE,
            DROP
        ],

            options: {
                name: "DropTarget",
                group: "default"
            },

            destroy: function () {
                var groupName = this.options.group,
                group = dropTargets[groupName] || dropAreas[groupName],
                i;

                if (group.length > 1) {
                    Widget.fn.destroy.call(this);

                    for (i = 0; i < group.length; i++) {
                        if (group[i] == this) {
                            group.splice(i, 1);
                            break;
                        }
                    }
                } else {
                    DropTarget.destroyGroup(groupName);
                }
            },

            _trigger: function (eventName, e) {
                var that = this,
                draggable = draggables[that.options.group];

                if (draggable) {
                    return that.trigger(eventName, extend({}, e.event, {
                        draggable: draggable,
                        dropTarget: e.dropTarget
                    }));
                }
            },

            _over: function (e) {
                this._trigger(DRAGENTER, e);
            },

            _out: function (e) {
                this._trigger(DRAGLEAVE, e);
            },

            _drop: function (e) {
                var that = this,
                draggable = draggables[that.options.group];

                if (draggable) {
                    draggable.dropped = !that._trigger(DROP, e);
                }
            }
        });

        DropTarget.destroyGroup = function (groupName) {
            var group = dropTargets[groupName] || dropAreas[groupName],
            i;

            if (group) {
                for (i = 0; i < group.length; i++) {
                    Widget.fn.destroy.call(group[i]);
                }

                group.length = 0;
                delete dropTargets[groupName];
                delete dropAreas[groupName];
            }
        };

        DropTarget._cache = dropTargets;

        var DropTargetArea = DropTarget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                var group = that.options.group;

                if (!(group in dropAreas)) {
                    dropAreas[group] = [that];
                } else {
                    dropAreas[group].push(that);
                }
            },

            options: {
                name: "DropTargetArea",
                group: "default",
                filter: null
            }
        });

        var Draggable = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                that._activated = false;

                that.userEvents = new UserEvents(that.element, {
                    global: true,
                    allowSelection: true,
                    filter: that.options.filter,
                    threshold: that.options.distance,
                    start: proxy(that._start, that),
                    hold: proxy(that._hold, that),
                    move: proxy(that._drag, that),
                    end: proxy(that._end, that),
                    cancel: proxy(that._cancel, that),
                    select: proxy(that._select, that)
                });

                that._afterEndHandler = proxy(that._afterEnd, that);
                that._captureEscape = proxy(that._captureEscape, that);
            },

            events: [
            HOLD,
            DRAGSTART,
            DRAG,
            DRAGEND,
            DRAGCANCEL
        ],

            options: {
                name: "Draggable",
                distance: (kendo.support.touch ? 0 : 5),
                group: "default",
                cursorOffset: null,
                axis: null,
                container: null,
                filter: null,
                ignore: null,
                holdToDrag: false,
                dropped: false
            },

            cancelHold: function () {
                this._activated = false;
            },

            _captureEscape: function (e) {
                var that = this;

                if (e.keyCode === kendo.keys.ESC) {
                    that._trigger(DRAGCANCEL, { event: e });
                    that.userEvents.cancel();
                }
            },

            _updateHint: function (e) {
                var that = this,
                coordinates,
                options = that.options,
                boundaries = that.boundaries,
                axis = options.axis,
                cursorOffset = that.options.cursorOffset;

                if (cursorOffset) {
                    coordinates = { left: e.x.location + cursorOffset.left, top: e.y.location + cursorOffset.top };
                } else {
                    that.hintOffset.left += e.x.delta;
                    that.hintOffset.top += e.y.delta;
                    coordinates = $.extend({}, that.hintOffset);
                }

                if (boundaries) {
                    coordinates.top = within(coordinates.top, boundaries.y);
                    coordinates.left = within(coordinates.left, boundaries.x);
                }

                if (axis === "x") {
                    delete coordinates.top;
                } else if (axis === "y") {
                    delete coordinates.left;
                }

                that.hint.css(coordinates);
            },

            _shouldIgnoreTarget: function (target) {
                var ignoreSelector = this.options.ignore;
                return ignoreSelector && $(target).is(ignoreSelector);
            },

            _select: function (e) {
                if (!this._shouldIgnoreTarget(e.event.target)) {
                    e.preventDefault();
                }
            },

            _start: function (e) {
                var that = this,
                options = that.options,
                container = options.container,
                hint = options.hint;

                if (this._shouldIgnoreTarget(e.touch.initialTouch) || (options.holdToDrag && !that._activated)) {
                    that.userEvents.cancel();
                    return;
                }

                that.currentTarget = e.target;
                that.currentTargetOffset = getOffset(that.currentTarget);

                if (hint) {
                    if (that.hint) {
                        that.hint.stop(true, true).remove();
                    }

                    that.hint = kendo.isFunction(hint) ? $(hint.call(that, that.currentTarget)) : hint;

                    var offset = getOffset(that.currentTarget);
                    that.hintOffset = offset;

                    that.hint.css({
                        position: "absolute",
                        zIndex: 20000, // the Window's z-index is 10000 and can be raised because of z-stacking
                        left: offset.left,
                        top: offset.top
                    })
                .appendTo(document.body);

                    that.angular("compile", function () {
                        that.hint.removeAttr("ng-repeat");
                        return {
                            elements: that.hint.get(),
                            scopeFrom: e.target
                        };
                    });
                }

                draggables[options.group] = that;

                that.dropped = false;

                if (container) {
                    that.boundaries = containerBoundaries(container, that.hint);
                }

                if (that._trigger(DRAGSTART, e)) {
                    that.userEvents.cancel();
                    that._afterEnd();
                }

                that.userEvents.capture();

                $(document).on(KEYUP, that._captureEscape);
            },

            _hold: function (e) {
                this.currentTarget = e.target;

                if (this._trigger(HOLD, e)) {
                    this.userEvents.cancel();
                } else {
                    this._activated = true;
                }
            },

            _drag: function (e) {
                var that = this;

                e.preventDefault();

                that._withDropTarget(e, function (target, targetElement) {
                    if (!target) {
                        if (lastDropTarget) {
                            lastDropTarget._trigger(DRAGLEAVE, extend(e, { dropTarget: $(lastDropTarget.targetElement) }));
                            lastDropTarget = null;
                        }
                        return;
                    }

                    if (lastDropTarget) {
                        if (targetElement === lastDropTarget.targetElement) {
                            return;
                        }

                        lastDropTarget._trigger(DRAGLEAVE, extend(e, { dropTarget: $(lastDropTarget.targetElement) }));
                    }

                    target._trigger(DRAGENTER, extend(e, { dropTarget: $(targetElement) }));
                    lastDropTarget = extend(target, { targetElement: targetElement });
                });

                that._trigger(DRAG, extend(e, { dropTarget: lastDropTarget }));

                if (that.hint) {
                    that._updateHint(e);
                }
            },

            _end: function (e) {
                var that = this;

                that._withDropTarget(e, function (target, targetElement) {
                    if (target) {
                        target._drop(extend({}, e, { dropTarget: $(targetElement) }));
                        lastDropTarget = null;
                    }
                });

                that._trigger(DRAGEND, e);
                that._cancel(e.event);
            },

            _cancel: function () {
                var that = this;

                that._activated = false;

                if (that.hint && !that.dropped) {
                    setTimeout(function () {
                        that.hint.stop(true, true).animate(that.currentTargetOffset, "fast", that._afterEndHandler);
                    }, 0);

                } else {
                    that._afterEnd();
                }
            },

            _trigger: function (eventName, e) {
                var that = this;

                return that.trigger(
                eventName, extend(
                {},
                e.event,
                {
                    x: e.x,
                    y: e.y,
                    currentTarget: that.currentTarget,
                    dropTarget: e.dropTarget
                }
            ));
            },

            _withDropTarget: function (e, callback) {
                var that = this,
                target, result,
                options = that.options,
                targets = dropTargets[options.group],
                areas = dropAreas[options.group];

                if (targets && targets.length || areas && areas.length) {

                    target = elementUnderCursor(e);

                    if (that.hint && contains(that.hint[0], target)) {
                        that.hint.hide();
                        target = elementUnderCursor(e);
                        // IE8 does not return the element in iframe from first attempt
                        if (!target) {
                            target = elementUnderCursor(e);
                        }
                        that.hint.show();
                    }

                    result = checkTarget(target, targets, areas);

                    if (result) {
                        callback(result.target, result.targetElement);
                    } else {
                        callback();
                    }
                }
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that._afterEnd();

                that.userEvents.destroy();

                that.currentTarget = null;
            },

            _afterEnd: function () {
                var that = this;

                if (that.hint) {
                    that.hint.remove();
                }

                delete draggables[that.options.group];

                that.trigger("destroy");
                $(document).off(KEYUP, that._captureEscape);
            }
        });

        kendo.ui.plugin(DropTarget);
        kendo.ui.plugin(DropTargetArea);
        kendo.ui.plugin(Draggable);
        kendo.TapCapture = TapCapture;
        kendo.containerBoundaries = containerBoundaries;

        extend(kendo.ui, {
            Pane: Pane,
            PaneDimensions: PaneDimensions,
            Movable: Movable
        });

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        mobile = kendo.mobile,
        fx = kendo.effects,
        ui = mobile.ui,
        proxy = $.proxy,
        extend = $.extend,
        Widget = ui.Widget,
        Class = kendo.Class,
        Movable = kendo.ui.Movable,
        Pane = kendo.ui.Pane,
        PaneDimensions = kendo.ui.PaneDimensions,
        Transition = fx.Transition,
        Animation = fx.Animation,
        abs = Math.abs,
        SNAPBACK_DURATION = 500,
        SCROLLBAR_OPACITY = 0.7,
        FRICTION = 0.96,
        VELOCITY_MULTIPLIER = 10,
        MAX_VELOCITY = 55,
        OUT_OF_BOUNDS_FRICTION = 0.5,
        ANIMATED_SCROLLER_PRECISION = 5,
        RELEASECLASS = "km-scroller-release",
        REFRESHCLASS = "km-scroller-refresh",
        PULL = "pull",
        CHANGE = "change",
        RESIZE = "resize",
        SCROLL = "scroll",
        MOUSE_WHEEL_ID = 2;

        var ZoomSnapBack = Animation.extend({
            init: function (options) {
                var that = this;
                Animation.fn.init.call(that);
                extend(that, options);

                that.userEvents.bind("gestureend", proxy(that.start, that));
                that.tapCapture.bind("press", proxy(that.cancel, that));
            },

            enabled: function () {
                return this.movable.scale < this.dimensions.minScale;
            },

            done: function () {
                return this.dimensions.minScale - this.movable.scale < 0.01;
            },

            tick: function () {
                var movable = this.movable;
                movable.scaleWith(1.1);
                this.dimensions.rescale(movable.scale);
            },

            onEnd: function () {
                var movable = this.movable;
                movable.scaleTo(this.dimensions.minScale);
                this.dimensions.rescale(movable.scale);
            }
        });

        var DragInertia = Animation.extend({
            init: function (options) {
                var that = this;

                Animation.fn.init.call(that);

                extend(that, options, {
                    transition: new Transition({
                        axis: options.axis,
                        movable: options.movable,
                        onEnd: function () { that._end(); }
                    })
                });

                that.tapCapture.bind("press", function () { that.cancel(); });
                that.userEvents.bind("end", proxy(that.start, that));
                that.userEvents.bind("gestureend", proxy(that.start, that));
                that.userEvents.bind("tap", proxy(that.onEnd, that));
            },

            onCancel: function () {
                this.transition.cancel();
            },

            freeze: function (location) {
                var that = this;
                that.cancel();
                that._moveTo(location);
            },

            onEnd: function () {
                var that = this;
                if (that.paneAxis.outOfBounds()) {
                    that._snapBack();
                } else {
                    that._end();
                }
            },

            done: function () {
                return abs(this.velocity) < 1;
            },

            start: function (e) {
                var that = this,
                velocity;

                if (!that.dimension.enabled) { return; }


                if (that.paneAxis.outOfBounds()) {
                    that._snapBack();
                } else {
                    velocity = e.touch.id === MOUSE_WHEEL_ID ? 0 : e.touch[that.axis].velocity;
                    that.velocity = Math.max(Math.min(velocity * that.velocityMultiplier, MAX_VELOCITY), -MAX_VELOCITY);

                    that.tapCapture.captureNext();
                    Animation.fn.start.call(that);
                }
            },

            tick: function () {
                var that = this,
                dimension = that.dimension,
                friction = that.paneAxis.outOfBounds() ? OUT_OF_BOUNDS_FRICTION : that.friction,
                delta = (that.velocity *= friction),
                location = that.movable[that.axis] + delta;

                if (!that.elastic && dimension.outOfBounds(location)) {
                    location = Math.max(Math.min(location, dimension.max), dimension.min);
                    that.velocity = 0;
                }

                that.movable.moveAxis(that.axis, location);
            },

            _end: function () {
                this.tapCapture.cancelCapture();
                this.end();
            },

            _snapBack: function () {
                var that = this,
                dimension = that.dimension,
                snapBack = that.movable[that.axis] > dimension.max ? dimension.max : dimension.min;
                that._moveTo(snapBack);
            },

            _moveTo: function (location) {
                this.transition.moveTo({ location: location, duration: SNAPBACK_DURATION, ease: Transition.easeOutExpo });
            }
        });

        var AnimatedScroller = Animation.extend({
            init: function (options) {
                var that = this;

                kendo.effects.Animation.fn.init.call(this);

                extend(that, options, {
                    origin: {},
                    destination: {},
                    offset: {}
                });
            },

            tick: function () {
                this._updateCoordinates();
                this.moveTo(this.origin);
            },

            done: function () {
                return abs(this.offset.y) < ANIMATED_SCROLLER_PRECISION && abs(this.offset.x) < ANIMATED_SCROLLER_PRECISION;
            },

            onEnd: function () {
                this.moveTo(this.destination);
                if (this.callback) {
                    this.callback.call();
                }
            },

            setCoordinates: function (from, to) {
                this.offset = {};
                this.origin = from;
                this.destination = to;
            },

            setCallback: function (callback) {
                if (callback && kendo.isFunction(callback)) {
                    this.callback = callback;
                } else {
                    callback = undefined;
                }
            },

            _updateCoordinates: function () {
                this.offset = {
                    x: (this.destination.x - this.origin.x) / 4,
                    y: (this.destination.y - this.origin.y) / 4
                };

                this.origin = {
                    y: this.origin.y + this.offset.y,
                    x: this.origin.x + this.offset.x
                };
            }
        });

        var ScrollBar = Class.extend({
            init: function (options) {
                var that = this,
                horizontal = options.axis === "x",
                element = $('<div class="km-touch-scrollbar km-' + (horizontal ? "horizontal" : "vertical") + '-scrollbar" />');

                extend(that, options, {
                    element: element,
                    elementSize: 0,
                    movable: new Movable(element),
                    scrollMovable: options.movable,
                    alwaysVisible: options.alwaysVisible,
                    size: horizontal ? "width" : "height"
                });

                that.scrollMovable.bind(CHANGE, proxy(that.refresh, that));
                that.container.append(element);
                if (options.alwaysVisible) {
                    that.show();
                }
            },

            refresh: function () {
                var that = this,
                axis = that.axis,
                dimension = that.dimension,
                paneSize = dimension.size,
                scrollMovable = that.scrollMovable,
                sizeRatio = paneSize / dimension.total,
                position = Math.round(-scrollMovable[axis] * sizeRatio),
                size = Math.round(paneSize * sizeRatio);

                if (sizeRatio >= 1) {
                    this.element.css("display", "none");
                } else {
                    this.element.css("display", "");
                }

                if (position + size > paneSize) {
                    size = paneSize - position;
                } else if (position < 0) {
                    size += position;
                    position = 0;
                }

                if (that.elementSize != size) {
                    that.element.css(that.size, size + "px");
                    that.elementSize = size;
                }

                that.movable.moveAxis(axis, position);
            },

            show: function () {
                this.element.css({ opacity: SCROLLBAR_OPACITY, visibility: "visible" });
            },

            hide: function () {
                if (!this.alwaysVisible) {
                    this.element.css({ opacity: 0 });
                }
            }
        });

        var Scroller = Widget.extend({
            init: function (element, options) {
                var that = this;
                Widget.fn.init.call(that, element, options);

                element = that.element;

                that._native = that.options.useNative && kendo.support.hasNativeScrolling;
                if (that._native) {
                    element.addClass("km-native-scroller")
                    .prepend('<div class="km-scroll-header"/>');

                    extend(that, {
                        scrollElement: element,
                        fixedContainer: element.children().first()
                    });

                    return;
                }

                element
                .css("overflow", "hidden")
                .addClass("km-scroll-wrapper")
                .wrapInner('<div class="km-scroll-container"/>')
                .prepend('<div class="km-scroll-header"/>');

                var inner = element.children().eq(1),

                tapCapture = new kendo.TapCapture(element),

                movable = new Movable(inner),

                dimensions = new PaneDimensions({
                    element: inner,
                    container: element,
                    forcedEnabled: that.options.zoom
                }),

                avoidScrolling = this.options.avoidScrolling,

                userEvents = new kendo.UserEvents(element, {
                    allowSelection: true,
                    preventDragEvent: true,
                    captureUpIfMoved: true,
                    multiTouch: that.options.zoom,
                    start: function (e) {
                        dimensions.refresh();

                        var velocityX = abs(e.x.velocity),
                            velocityY = abs(e.y.velocity),
                            horizontalSwipe = velocityX * 2 >= velocityY,
                            originatedFromFixedContainer = $.contains(that.fixedContainer[0], e.event.target),
                            verticalSwipe = velocityY * 2 >= velocityX;


                        if (!originatedFromFixedContainer && !avoidScrolling(e) && that.enabled && (dimensions.x.enabled && horizontalSwipe || dimensions.y.enabled && verticalSwipe)) {
                            userEvents.capture();
                        } else {
                            userEvents.cancel();
                        }
                    }
                }),

                pane = new Pane({
                    movable: movable,
                    dimensions: dimensions,
                    userEvents: userEvents,
                    elastic: that.options.elastic
                }),

                zoomSnapBack = new ZoomSnapBack({
                    movable: movable,
                    dimensions: dimensions,
                    userEvents: userEvents,
                    tapCapture: tapCapture
                }),

                animatedScroller = new AnimatedScroller({
                    moveTo: function (coordinates) {
                        that.scrollTo(coordinates.x, coordinates.y);
                    }
                });

                movable.bind(CHANGE, function () {
                    that.scrollTop = -movable.y;
                    that.scrollLeft = -movable.x;

                    that.trigger(SCROLL, {
                        scrollTop: that.scrollTop,
                        scrollLeft: that.scrollLeft
                    });
                });

                if (that.options.mousewheelScrolling) {
                    element.on("DOMMouseScroll mousewheel", proxy(this, "_wheelScroll"));
                }

                extend(that, {
                    movable: movable,
                    dimensions: dimensions,
                    zoomSnapBack: zoomSnapBack,
                    animatedScroller: animatedScroller,
                    userEvents: userEvents,
                    pane: pane,
                    tapCapture: tapCapture,
                    pulled: false,
                    enabled: true,
                    scrollElement: inner,
                    scrollTop: 0,
                    scrollLeft: 0,
                    fixedContainer: element.children().first()
                });

                that._initAxis("x");
                that._initAxis("y");

                // build closure
                that._wheelEnd = function () {
                    that._wheel = false;
                    that.userEvents.end(0, that._wheelY);
                };

                dimensions.refresh();

                if (that.options.pullToRefresh) {
                    that._initPullToRefresh();
                }
            },

            _wheelScroll: function (e) {
                if (!this._wheel) {
                    this._wheel = true;
                    this._wheelY = 0;
                    this.userEvents.press(0, this._wheelY);
                }

                clearTimeout(this._wheelTimeout);
                this._wheelTimeout = setTimeout(this._wheelEnd, 50);

                var delta = kendo.wheelDeltaY(e);

                if (delta) {
                    this._wheelY += delta;
                    this.userEvents.move(0, this._wheelY);
                }

                e.preventDefault();
            },

            makeVirtual: function () {
                this.dimensions.y.makeVirtual();
            },

            virtualSize: function (min, max) {
                this.dimensions.y.virtualSize(min, max);
            },

            height: function () {
                return this.dimensions.y.size;
            },

            scrollHeight: function () {
                return this.scrollElement[0].scrollHeight;
            },

            scrollWidth: function () {
                return this.scrollElement[0].scrollWidth;
            },

            options: {
                name: "Scroller",
                zoom: false,
                pullOffset: 140,
                visibleScrollHints: false,
                elastic: true,
                useNative: false,
                mousewheelScrolling: true,
                avoidScrolling: function () { return false; },
                pullToRefresh: false,
                messages: {
                    pullTemplate: "Pull to refresh",
                    releaseTemplate: "Release to refresh",
                    refreshTemplate: "Refreshing"
                }
            },

            events: [
            PULL,
            SCROLL,
            RESIZE
        ],

            _resize: function () {
                if (!this._native) {
                    this.contentResized();
                }
            },

            setOptions: function (options) {
                var that = this;
                Widget.fn.setOptions.call(that, options);
                if (options.pullToRefresh) {
                    that._initPullToRefresh();
                }
            },

            reset: function () {
                if (this._native) {
                    this.scrollElement.scrollTop(0);
                } else {
                    this.movable.moveTo({ x: 0, y: 0 });
                    this._scale(1);
                }
            },

            contentResized: function () {
                this.dimensions.refresh();
                if (this.pane.x.outOfBounds()) {
                    this.movable.moveAxis("x", this.dimensions.x.min);
                }

                if (this.pane.y.outOfBounds()) {
                    this.movable.moveAxis("y", this.dimensions.y.min);
                }
            },

            zoomOut: function () {
                var dimensions = this.dimensions;
                dimensions.refresh();
                this._scale(dimensions.fitScale);
                this.movable.moveTo(dimensions.centerCoordinates());
            },

            enable: function () {
                this.enabled = true;
            },

            disable: function () {
                this.enabled = false;
            },

            scrollTo: function (x, y) {
                if (this._native) {
                    this.scrollElement.scrollLeft(abs(x));
                    this.scrollElement.scrollTop(abs(y));
                } else {
                    this.dimensions.refresh();
                    this.movable.moveTo({ x: x, y: y });
                }
            },

            animatedScrollTo: function (x, y, callback) {
                var from,
                to;

                if (this._native) {
                    this.scrollTo(x, y);
                } else {
                    from = { x: this.movable.x, y: this.movable.y };
                    to = { x: x, y: y };

                    this.animatedScroller.setCoordinates(from, to);
                    this.animatedScroller.setCallback(callback);
                    this.animatedScroller.start();
                }
            },

            pullHandled: function () {
                var that = this;
                that.refreshHint.removeClass(REFRESHCLASS);
                that.hintContainer.html(that.pullTemplate({}));
                that.yinertia.onEnd();
                that.xinertia.onEnd();
                that.userEvents.cancel();
            },

            destroy: function () {
                Widget.fn.destroy.call(this);
                if (this.userEvents) {
                    this.userEvents.destroy();
                }
            },

            _scale: function (scale) {
                this.dimensions.rescale(scale);
                this.movable.scaleTo(scale);
            },

            _initPullToRefresh: function () {
                var that = this;

                that.dimensions.y.forceEnabled();
                that.pullTemplate = kendo.template(that.options.messages.pullTemplate);
                that.releaseTemplate = kendo.template(that.options.messages.releaseTemplate);
                that.refreshTemplate = kendo.template(that.options.messages.refreshTemplate);

                that.scrollElement.prepend('<span class="km-scroller-pull"><span class="km-icon"></span><span class="km-loading-left"></span><span class="km-loading-right"></span><span class="km-template">' + that.pullTemplate({}) + '</span></span>');
                that.refreshHint = that.scrollElement.children().first();
                that.hintContainer = that.refreshHint.children(".km-template");

                that.pane.y.bind("change", proxy(that._paneChange, that));
                that.userEvents.bind("end", proxy(that._dragEnd, that));
            },

            _dragEnd: function () {
                var that = this;

                if (!that.pulled) {
                    return;
                }

                that.pulled = false;
                that.refreshHint.removeClass(RELEASECLASS).addClass(REFRESHCLASS);
                that.hintContainer.html(that.refreshTemplate({}));
                that.yinertia.freeze(that.options.pullOffset / 2);
                that.trigger("pull");
            },

            _paneChange: function () {
                var that = this;

                if (that.movable.y / OUT_OF_BOUNDS_FRICTION > that.options.pullOffset) {
                    if (!that.pulled) {
                        that.pulled = true;
                        that.refreshHint.removeClass(REFRESHCLASS).addClass(RELEASECLASS);
                        that.hintContainer.html(that.releaseTemplate({}));
                    }
                } else if (that.pulled) {
                    that.pulled = false;
                    that.refreshHint.removeClass(RELEASECLASS);
                    that.hintContainer.html(that.pullTemplate({}));
                }
            },

            _initAxis: function (axis) {
                var that = this,
                movable = that.movable,
                dimension = that.dimensions[axis],
                tapCapture = that.tapCapture,
                paneAxis = that.pane[axis],
                scrollBar = new ScrollBar({
                    axis: axis,
                    movable: movable,
                    dimension: dimension,
                    container: that.element,
                    alwaysVisible: that.options.visibleScrollHints
                });

                dimension.bind(CHANGE, function () {
                    scrollBar.refresh();
                });

                paneAxis.bind(CHANGE, function () {
                    scrollBar.show();
                });

                that[axis + "inertia"] = new DragInertia({
                    axis: axis,
                    paneAxis: paneAxis,
                    movable: movable,
                    tapCapture: tapCapture,
                    userEvents: that.userEvents,
                    dimension: dimension,
                    elastic: that.options.elastic,
                    friction: that.options.friction || FRICTION,
                    velocityMultiplier: that.options.velocityMultiplier || VELOCITY_MULTIPLIER,
                    end: function () {
                        scrollBar.hide();
                        that.trigger("scrollEnd", {
                            axis: axis,
                            scrollTop: that.scrollTop,
                            scrollLeft: that.scrollLeft
                        });
                    }
                });
            }
        });

        ui.plugin(Scroller);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        proxy = $.proxy,
        isRtl = false,
        NS = ".kendoGroupable",
        CHANGE = "change",
        indicatorTmpl = kendo.template('<div class="k-group-indicator" data-#=data.ns#field="${data.field}" data-#=data.ns#title="${data.title || ""}" data-#=data.ns#dir="${data.dir || "asc"}">' +
                '<a href="\\#" class="k-link">' +
                    '<span class="k-icon k-si-arrow-${(data.dir || "asc") == "asc" ? "n" : "s"}">(sorted ${(data.dir || "asc") == "asc" ? "ascending": "descending"})</span>' +
                    '${data.title ? data.title: data.field}' +
                '</a>' +
                '<a class="k-button k-button-icon k-button-bare">' +
                    '<span class="k-icon k-group-delete"></span>' +
                '</a>' +
             '</div>', { useWithBlock: false }),
        hint = function (target) {
            return $('<div class="k-header k-drag-clue" />')
                .css({
                    width: target.width(),
                    paddingLeft: target.css("paddingLeft"),
                    paddingRight: target.css("paddingRight"),
                    lineHeight: target.height() + "px",
                    paddingTop: target.css("paddingTop"),
                    paddingBottom: target.css("paddingBottom")
                })
                .html(target.attr(kendo.attr("title")) || target.attr(kendo.attr("field")))
                .prepend('<span class="k-icon k-drag-status k-denied" />');
        },
        dropCue = $('<div class="k-grouping-dropclue"/>'),
        nameSpecialCharRegExp = /("|\%|'|\[|\]|\$|\.|\,|\:|\;|\+|\*|\&|\!|\#|\(|\)|<|>|\=|\?|\@|\^|\{|\}|\~|\/|\||`)/g;

        function dropCueOffsetTop(element) {
            return element.position().top + 3;
        }

        var Groupable = Widget.extend({
            init: function (element, options) {
                var that = this,
                groupContainer,
                group = kendo.guid(),
                intializePositions = proxy(that._intializePositions, that),
                draggable,
                horizontalCuePosition,
                dropCuePositions = that._dropCuePositions = [];

                Widget.fn.init.call(that, element, options);

                isRtl = kendo.support.isRtl(element);
                horizontalCuePosition = isRtl ? "right" : "left";

                that.draggable = draggable = that.options.draggable || new kendo.ui.Draggable(that.element, {
                    filter: that.options.draggableElements,
                    hint: hint,
                    group: group
                });

                that.groupContainer = $(that.options.groupContainer, that.element)
                .kendoDropTarget({
                    group: draggable.options.group,
                    dragenter: function (e) {
                        if (that._canDrag(e.draggable.currentTarget)) {
                            e.draggable.hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
                            dropCue.css("top", dropCueOffsetTop(that.groupContainer)).css(horizontalCuePosition, 0).appendTo(that.groupContainer);
                        }
                    },
                    dragleave: function (e) {
                        e.draggable.hint.find(".k-drag-status").removeClass("k-add").addClass("k-denied");
                        dropCue.remove();
                    },
                    drop: function (e) {
                        var targetElement = e.draggable.currentTarget,
                            field = targetElement.attr(kendo.attr("field")),
                            title = targetElement.attr(kendo.attr("title")),
                            sourceIndicator = that.indicator(field),
                            dropCuePositions = that._dropCuePositions,
                            lastCuePosition = dropCuePositions[dropCuePositions.length - 1],
                            position;

                        if (!targetElement.hasClass("k-group-indicator") && !that._canDrag(targetElement)) {
                            return;
                        }
                        if (lastCuePosition) {
                            position = that._dropCuePosition(kendo.getOffset(dropCue).left + parseInt(lastCuePosition.element.css("marginLeft"), 10) * (isRtl ? -1 : 1) + parseInt(lastCuePosition.element.css("marginRight"), 10));
                            if (position && that._canDrop($(sourceIndicator), position.element, position.left)) {
                                if (position.before) {
                                    position.element.before(sourceIndicator || that.buildIndicator(field, title));
                                } else {
                                    position.element.after(sourceIndicator || that.buildIndicator(field, title));
                                }

                                that._change();
                            }
                        } else {
                            that.groupContainer.append(that.buildIndicator(field, title));
                            that._change();
                        }
                    }
                })
                .kendoDraggable({
                    filter: "div.k-group-indicator",
                    hint: hint,
                    group: draggable.options.group,
                    dragcancel: proxy(that._dragCancel, that),
                    dragstart: function (e) {
                        var element = e.currentTarget,
                            marginLeft = parseInt(element.css("marginLeft"), 10),
                            elementPosition = element.position(),
                            left = isRtl ? elementPosition.left - marginLeft : elementPosition.left + element.outerWidth();

                        intializePositions();
                        dropCue.css({ top: dropCueOffsetTop(that.groupContainer), left: left }).appendTo(that.groupContainer);
                        this.hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
                    },
                    dragend: function () {
                        that._dragEnd(this);
                    },
                    drag: proxy(that._drag, that)
                })
                .on("click" + NS, ".k-button", function (e) {
                    e.preventDefault();
                    that._removeIndicator($(this).parent());
                })
                .on("click" + NS, ".k-link", function (e) {
                    var current = $(this).parent(),
                        newIndicator = that.buildIndicator(current.attr(kendo.attr("field")), current.attr(kendo.attr("title")), current.attr(kendo.attr("dir")) == "asc" ? "desc" : "asc");

                    current.before(newIndicator).remove();
                    that._change();
                    e.preventDefault();
                });

                draggable.bind(["dragend", "dragcancel", "dragstart", "drag"],
            {
                dragend: function () {
                    that._dragEnd(this);
                },
                dragcancel: proxy(that._dragCancel, that),
                dragstart: function (e) {
                    var element, marginRight, left;

                    if (!that.options.allowDrag && !that._canDrag(e.currentTarget)) {
                        e.preventDefault();
                        return;
                    }

                    intializePositions();
                    if (dropCuePositions.length) {
                        element = dropCuePositions[dropCuePositions.length - 1].element;
                        marginRight = parseInt(element.css("marginRight"), 10);
                        left = element.position().left + element.outerWidth() + marginRight;
                    } else {
                        left = 0;
                    }
                },
                drag: proxy(that._drag, that)
            });

                that.dataSource = that.options.dataSource;

                if (that.dataSource && that._refreshHandler) {
                    that.dataSource.unbind(CHANGE, that._refreshHandler);
                } else {
                    that._refreshHandler = proxy(that.refresh, that);
                }

                if (that.dataSource) {
                    that.dataSource.bind("change", that._refreshHandler);
                    that.refresh();
                }
            },

            refresh: function () {
                var that = this,
                dataSource = that.dataSource;

                if (that.groupContainer) {
                    that.groupContainer.empty().append(
                    $.map(dataSource.group() || [], function (item) {
                        var fieldName = item.field.replace(nameSpecialCharRegExp, "\\$1");
                        var element = that.element.find(that.options.filter).filter("[" + kendo.attr("field") + "=" + fieldName + "]");
                        return that.buildIndicator(item.field, element.attr(kendo.attr("title")), item.dir);
                    }).join("")
                );
                }
                that._invalidateGroupContainer();
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.groupContainer.off(NS);

                if (that.groupContainer.data("kendoDropTarget")) {
                    that.groupContainer.data("kendoDropTarget").destroy();
                }

                if (that.groupContainer.data("kendoDraggable")) {
                    that.groupContainer.data("kendoDraggable").destroy();
                }

                if (!that.options.draggable) {
                    that.draggable.destroy();
                }

                if (that.dataSource && that._refreshHandler) {
                    that.dataSource.unbind("change", that._refreshHandler);
                    that._refreshHandler = null;
                }

                that.groupContainer = that.element = that.draggable = null;
            },

            options: {
                name: "Groupable",
                filter: "th",
                draggableElements: "th",
                messages: {
                    empty: "Drag a column header and drop it here to group by that column"
                }
            },

            indicator: function (field) {
                var indicators = $(".k-group-indicator", this.groupContainer);
                return $.grep(indicators, function (item) {
                    return $(item).attr(kendo.attr("field")) === field;
                })[0];
            },
            buildIndicator: function (field, title, dir) {
                return indicatorTmpl({ field: field.replace(/"/g, "'"), dir: dir, title: title, ns: kendo.ns });
            },
            descriptors: function () {
                var that = this,
                indicators = $(".k-group-indicator", that.groupContainer),
                aggregates,
                names,
                field,
                idx,
                length;

                aggregates = that.element.find(that.options.filter).map(function () {
                    var cell = $(this),
                    aggregate = cell.attr(kendo.attr("aggregates")),
                    member = cell.attr(kendo.attr("field"));

                    if (aggregate && aggregate !== "") {
                        names = aggregate.split(",");
                        aggregate = [];
                        for (idx = 0, length = names.length; idx < length; idx++) {
                            aggregate.push({ field: member, aggregate: names[idx] });
                        }
                    }
                    return aggregate;
                }).toArray();

                return $.map(indicators, function (item) {
                    item = $(item);
                    field = item.attr(kendo.attr("field"));

                    return {
                        field: field,
                        dir: item.attr(kendo.attr("dir")),
                        aggregates: aggregates || []
                    };
                });
            },
            _removeIndicator: function (indicator) {
                var that = this;
                indicator.remove();
                that._invalidateGroupContainer();
                that._change();
            },
            _change: function () {
                var that = this;
                if (that.dataSource) {
                    that.dataSource.group(that.descriptors());
                }
            },
            _dropCuePosition: function (position) {
                var dropCuePositions = this._dropCuePositions;
                if (!dropCue.is(":visible") || dropCuePositions.length === 0) {
                    return;
                }

                position = Math.ceil(position);

                var lastCuePosition = dropCuePositions[dropCuePositions.length - 1],
                left = lastCuePosition.left,
                right = lastCuePosition.right,
                marginLeft = parseInt(lastCuePosition.element.css("marginLeft"), 10),
                marginRight = parseInt(lastCuePosition.element.css("marginRight"), 10);

                if (position >= right && !isRtl || position < left && isRtl) {
                    position = {
                        left: lastCuePosition.element.position().left + (!isRtl ? lastCuePosition.element.outerWidth() + marginRight : -marginLeft),
                        element: lastCuePosition.element,
                        before: false
                    };
                } else {
                    position = $.grep(dropCuePositions, function (item) {
                        return (item.left <= position && position <= item.right) || (isRtl && position > item.right);
                    })[0];

                    if (position) {
                        position = {
                            left: isRtl ? position.element.position().left + position.element.outerWidth() + marginRight : position.element.position().left - marginLeft,
                            element: position.element,
                            before: true
                        };
                    }
                }

                return position;
            },
            _drag: function (event) {
                var position = this._dropCuePosition(event.x.location);

                if (position) {
                    dropCue.css({ left: position.left, right: "auto" });
                }
            },
            _canDrag: function (element) {
                var field = element.attr(kendo.attr("field"));

                return element.attr(kendo.attr("groupable")) != "false" &&
                field &&
                (element.hasClass("k-group-indicator") ||
                    !this.indicator(field));
            },
            _canDrop: function (source, target, position) {
                var next = source.next(),
                result = source[0] !== target[0] && (!next[0] || target[0] !== next[0] || (!isRtl && position > next.position().left || isRtl && position < next.position().left));
                return result;
            },
            _dragEnd: function (draggable) {
                var that = this,
                field = draggable.currentTarget.attr(kendo.attr("field")),
                sourceIndicator = that.indicator(field);

                if (draggable !== that.options.draggable && !draggable.dropped && sourceIndicator) {
                    that._removeIndicator($(sourceIndicator));
                }

                that._dragCancel();
            },
            _dragCancel: function () {
                dropCue.remove();
                this._dropCuePositions = [];
            },
            _intializePositions: function () {
                var that = this,
                indicators = $(".k-group-indicator", that.groupContainer),
                left;

                that._dropCuePositions = $.map(indicators, function (item) {
                    item = $(item);
                    left = kendo.getOffset(item).left;
                    return {
                        left: parseInt(left, 10),
                        right: parseInt(left + item.outerWidth(), 10),
                        element: item
                    };
                });
            },
            _invalidateGroupContainer: function () {
                var groupContainer = this.groupContainer;
                if (groupContainer && groupContainer.is(":empty")) {
                    groupContainer.html(this.options.messages.empty);
                }
            }
        });

        kendo.ui.plugin(Groupable);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        getOffset = kendo.getOffset,
        Widget = kendo.ui.Widget,
        CHANGE = "change",
        KREORDERABLE = "k-reorderable";

        function toggleHintClass(hint, denied) {
            hint = $(hint);

            if (denied) {
                hint.find(".k-drag-status").removeClass("k-add").addClass("k-denied");
            } else {
                hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
            }
        }

        var Reorderable = Widget.extend({
            init: function (element, options) {
                var that = this,
                draggable,
                group = kendo.guid() + "-reorderable";

                Widget.fn.init.call(that, element, options);

                element = that.element.addClass(KREORDERABLE);
                options = that.options;

                that.draggable = draggable = options.draggable || new kendo.ui.Draggable(element, {
                    group: group,
                    filter: options.filter,
                    hint: options.hint
                });

                that.reorderDropCue = $('<div class="k-reorder-cue"><div class="k-icon k-i-arrow-s"></div><div class="k-icon k-i-arrow-n"></div></div>');

                element.find(draggable.options.filter).kendoDropTarget({
                    group: draggable.options.group,
                    dragenter: function (e) {
                        if (!that._draggable) {
                            return;
                        }

                        var dropTarget = this.element, offset;
                        var denied = !that._dropTargetAllowed(dropTarget) || that._isLastDraggable();

                        toggleHintClass(e.draggable.hint, denied);
                        if (!denied) {
                            offset = getOffset(dropTarget);
                            var left = offset.left;

                            if (options.inSameContainer && !options.inSameContainer({
                                source: dropTarget,
                                target: that._draggable,
                                sourceIndex: that._index(dropTarget),
                                targetIndex: that._index(that._draggable)
                            })) {
                                that._dropTarget = dropTarget;
                            } else {
                                if (that._index(dropTarget) > that._index(that._draggable)) {
                                    left += dropTarget.outerWidth();
                                }
                            }

                            that.reorderDropCue.css({
                                height: dropTarget.outerHeight(),
                                top: offset.top,
                                left: left
                            })
                        .appendTo(document.body);
                        }
                    },
                    dragleave: function (e) {
                        toggleHintClass(e.draggable.hint, true);
                        that.reorderDropCue.remove();
                        that._dropTarget = null;
                    },
                    drop: function () {
                        that._dropTarget = null;
                        if (!that._draggable) {
                            return;
                        }
                        var dropTarget = this.element;
                        var draggable = that._draggable;
                        var containerChange = false;

                        if (that._dropTargetAllowed(dropTarget) && !that._isLastDraggable()) {
                            that.trigger(CHANGE, {
                                element: that._draggable,
                                target: dropTarget,
                                oldIndex: that._index(draggable),
                                newIndex: that._index(dropTarget),
                                position: getOffset(that.reorderDropCue).left > getOffset(dropTarget).left ? "after" : "before"
                            });
                        }
                    }
                });

                draggable.bind(["dragcancel", "dragend", "dragstart", "drag"],
                {
                    dragcancel: function () {
                        that.reorderDropCue.remove();
                        that._draggable = null;
                        that._elements = null;
                    },
                    dragend: function () {
                        that.reorderDropCue.remove();
                        that._draggable = null;
                        that._elements = null;
                    },
                    dragstart: function (e) {
                        that._draggable = e.currentTarget;
                        that._elements = that.element.find(that.draggable.options.filter);
                    },
                    drag: function (e) {
                        if (!that._dropTarget || this.hint.find(".k-drag-status").hasClass("k-denied")) {
                            return;
                        }

                        var dropStartOffset = getOffset(that._dropTarget).left;
                        var width = that._dropTarget.outerWidth();

                        if (e.pageX > dropStartOffset + width / 2) {
                            that.reorderDropCue.css({ left: dropStartOffset + width });
                        } else {
                            that.reorderDropCue.css({ left: dropStartOffset });
                        }
                    }
                }
            );
            },

            options: {
                name: "Reorderable",
                filter: "*"
            },

            events: [
            CHANGE
        ],

            _isLastDraggable: function () {
                var inSameContainer = this.options.inSameContainer,
                draggable = this._draggable[0],
                elements = this._elements.get(),
                found = false,
                item;

                if (!inSameContainer) {
                    return false;
                }

                while (!found && elements.length > 0) {
                    item = elements.pop();
                    found = draggable !== item && inSameContainer({
                        source: draggable,
                        target: item,
                        sourceIndex: this._index(draggable),
                        targetIndex: this._index(item)
                    });
                }

                return !found;
            },

            _dropTargetAllowed: function (dropTarget) {
                var inSameContainer = this.options.inSameContainer,
                dragOverContainers = this.options.dragOverContainers,
                draggable = this._draggable;

                if (draggable[0] === dropTarget[0]) {
                    return false;
                }

                if (!inSameContainer || !dragOverContainers) {
                    return true;
                }

                if (inSameContainer({ source: draggable,
                    target: dropTarget,
                    sourceIndex: this._index(draggable),
                    targetIndex: this._index(dropTarget)
                })) {
                    return true;
                }

                return dragOverContainers(this._index(draggable), this._index(dropTarget));
            },

            _index: function (element) {
                return this._elements.index(element);
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.element.find(that.draggable.options.filter).each(function () {
                    var item = $(this);
                    if (item.data("kendoDropTarget")) {
                        item.data("kendoDropTarget").destroy();
                    }
                });

                if (that.draggable) {
                    that.draggable.destroy();

                    that.draggable.element = that.draggable = null;
                }
                that.elements = that.reorderDropCue = that._elements = that._draggable = null;
            }
        });

        kendo.ui.plugin(Reorderable);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        proxy = $.proxy,
        isFunction = kendo.isFunction,
        extend = $.extend,
        HORIZONTAL = "horizontal",
        VERTICAL = "vertical",
        START = "start",
        RESIZE = "resize",
        RESIZEEND = "resizeend";

        var Resizable = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                that.orientation = that.options.orientation.toLowerCase() != VERTICAL ? HORIZONTAL : VERTICAL;
                that._positionMouse = that.orientation == HORIZONTAL ? "x" : "y";
                that._position = that.orientation == HORIZONTAL ? "left" : "top";
                that._sizingDom = that.orientation == HORIZONTAL ? "outerWidth" : "outerHeight";

                that.draggable = new ui.Draggable(element, {
                    distance: 0,
                    filter: options.handle,
                    drag: proxy(that._resize, that),
                    dragcancel: proxy(that._cancel, that),
                    dragstart: proxy(that._start, that),
                    dragend: proxy(that._stop, that)
                });

                that.userEvents = that.draggable.userEvents;
            },

            events: [
            RESIZE,
            RESIZEEND,
            START
        ],

            options: {
                name: "Resizable",
                orientation: HORIZONTAL
            },

            resize: function () {
                // Overrides base widget resize
            },

            _max: function (e) {
                var that = this,
                hintSize = that.hint ? that.hint[that._sizingDom]() : 0,
                size = that.options.max;

                return isFunction(size) ? size(e) : size !== undefined ? (that._initialElementPosition + size) - hintSize : size;
            },

            _min: function (e) {
                var that = this,
                size = that.options.min;

                return isFunction(size) ? size(e) : size !== undefined ? that._initialElementPosition + size : size;
            },

            _start: function (e) {
                var that = this,
                hint = that.options.hint,
                el = $(e.currentTarget);

                that._initialElementPosition = el.position()[that._position];
                that._initialMousePosition = e[that._positionMouse].startLocation;

                if (hint) {
                    that.hint = isFunction(hint) ? $(hint(el)) : hint;

                    that.hint.css({
                        position: "absolute"
                    })
                .css(that._position, that._initialElementPosition)
                .appendTo(that.element);
                }

                that.trigger(START, e);

                that._maxPosition = that._max(e);
                that._minPosition = that._min(e);

                $(document.body).css("cursor", el.css("cursor"));
            },

            _resize: function (e) {
                var that = this,
                maxPosition = that._maxPosition,
                minPosition = that._minPosition,
                currentPosition = that._initialElementPosition + (e[that._positionMouse].location - that._initialMousePosition),
                position;

                position = minPosition !== undefined ? Math.max(minPosition, currentPosition) : currentPosition;
                that.position = position = maxPosition !== undefined ? Math.min(maxPosition, position) : position;

                if (that.hint) {
                    that.hint.toggleClass(that.options.invalidClass || "", position == maxPosition || position == minPosition)
                         .css(that._position, position);
                }

                that.resizing = true;
                that.trigger(RESIZE, extend(e, { position: position }));
            },

            _stop: function (e) {
                var that = this;

                if (that.hint) {
                    that.hint.remove();
                }

                that.resizing = false;
                that.trigger(RESIZEEND, extend(e, { position: that.position }));
                $(document.body).css("cursor", "");
            },

            _cancel: function (e) {
                var that = this;

                if (that.hint) {
                    that.position = undefined;
                    that.hint.css(that._position, that._initialElementPosition);
                    that._stop(e);
                }
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                if (that.draggable) {
                    that.draggable.destroy();
                }
            },

            press: function (target) {
                if (!target) {
                    return;
                }

                var position = target.position(),
                that = this;

                that.userEvents.press(position.left, position.top, target[0]);
                that.targetPosition = position;
                that.target = target;
            },

            move: function (delta) {
                var that = this,
                orientation = that._position,
                position = that.targetPosition,
                current = that.position;

                if (current === undefined) {
                    current = position[orientation];
                }

                position[orientation] = current + delta;

                that.userEvents.move(position.left, position.top);
            },

            end: function () {
                this.userEvents.end();
                this.target = this.position = undefined;
            }
        });

        kendo.ui.plugin(Resizable);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,

        START = "start",
        BEFORE_MOVE = "beforeMove",
        MOVE = "move",
        END = "end",
        CHANGE = "change",
        CANCEL = "cancel",

        ACTION_SORT = "sort",
        ACTION_REMOVE = "remove",
        ACTION_RECEIVE = "receive",

        DEFAULT_FILTER = ">*",
        MISSING_INDEX = -1;

        function containsOrEqualTo(parent, child) {
            try {
                return $.contains(parent, child) || parent == child;
            } catch (e) {
                return false;
            }
        }

        function defaultHint(element) {
            return element.clone();
        }

        function defaultPlaceholder(element) {
            return element.clone().removeAttr("id").css("visibility", "hidden");
        }

        var Sortable = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                if (!that.options.placeholder) {
                    that.options.placeholder = defaultPlaceholder;
                }

                if (!that.options.hint) {
                    that.options.hint = defaultHint;
                }

                that._draggable = that._createDraggable();
            },

            events: [
            START,
            BEFORE_MOVE,
            MOVE,
            END,
            CHANGE,
            CANCEL
        ],

            options: {
                name: "Sortable",
                hint: null,
                placeholder: null,
                filter: DEFAULT_FILTER,
                holdToDrag: false,
                disabled: null,
                container: null,
                connectWith: null,
                handler: null,
                cursorOffset: null,
                axis: null,
                ignore: null,
                cursor: "auto"
            },

            destroy: function () {
                this._draggable.destroy();
                Widget.fn.destroy.call(this);
            },

            _createDraggable: function () {
                var that = this,
                element = that.element,
                options = that.options;

                return new kendo.ui.Draggable(element, {
                    filter: options.filter,
                    hint: kendo.isFunction(options.hint) ? options.hint : $(options.hint),
                    holdToDrag: options.holdToDrag,
                    container: options.container ? $(options.container) : null,
                    cursorOffset: options.cursorOffset,
                    axis: options.axis,
                    ignore: options.ignore,
                    dragstart: $.proxy(that._dragstart, that),
                    dragcancel: $.proxy(that._dragcancel, that),
                    drag: $.proxy(that._drag, that),
                    dragend: $.proxy(that._dragend, that)
                });
            },

            _dragstart: function (e) {
                var draggedElement = this.draggedElement = e.currentTarget,
                target = e.target || kendo.elementUnderCursor(e),
                disabled = this.options.disabled,
                handler = this.options.handler,
                _placeholder = this.options.placeholder,
                placeholder = this.placeholder = kendo.isFunction(_placeholder) ? $(_placeholder.call(this, draggedElement)) : $(_placeholder);

                if (disabled && draggedElement.is(disabled)) {
                    e.preventDefault();
                } else if (handler && !$(target).is(handler)) {
                    e.preventDefault();
                } else {

                    if (this.trigger(START, { item: draggedElement, draggableEvent: e })) {
                        e.preventDefault();
                    } else {
                        draggedElement.css("display", "none");
                        draggedElement.before(placeholder);

                        this._setCursor();
                    }

                }
            },

            _dragcancel: function (e) {
                this._cancel();
                this.trigger(CANCEL, { item: this.draggedElement });

                this._resetCursor();
            },

            _drag: function (e) {
                var draggedElement = this.draggedElement,
                target = this._findTarget(e),
                targetCenter,
                cursorOffset = { left: e.x.location, top: e.y.location },
                offsetDelta,
                axisDelta = { x: e.x.delta, y: e.y.delta },
                direction,
                sibling,
                getSibling,
                axis = this.options.axis,
                eventData = { item: draggedElement, list: this, draggableEvent: e };

                if (axis === "x" || axis === "y") {
                    this._movementByAxis(axis, cursorOffset, axisDelta[axis], eventData);
                    return;
                }

                if (target) {
                    targetCenter = this._getElementCenter(target.element);

                    offsetDelta = {
                        left: Math.round(cursorOffset.left - targetCenter.left),
                        top: Math.round(cursorOffset.top - targetCenter.top)
                    };

                    $.extend(eventData, { target: target.element });

                    if (target.appendToBottom) {
                        this._movePlaceholder(target, null, eventData);
                        return;
                    }

                    if (target.appendAfterHidden) {
                        this._movePlaceholder(target, "next", eventData);
                    }

                    if (this._isFloating(target.element)) { //horizontal
                        if (axisDelta.x < 0 && offsetDelta.left < 0) {
                            direction = "prev";
                        } else if (axisDelta.x > 0 && offsetDelta.left > 0) {
                            direction = "next";
                        }
                    } else { //vertical
                        if (axisDelta.y < 0 && offsetDelta.top < 0) {
                            direction = "prev";
                        } else if (axisDelta.y > 0 && offsetDelta.top > 0) {
                            direction = "next";
                        }
                    }

                    if (direction) {
                        getSibling = (direction === "prev") ? jQuery.fn.prev : jQuery.fn.next;

                        sibling = getSibling.call(target.element);

                        //find the prev/next visible sibling
                        while (sibling.length && !sibling.is(":visible")) {
                            sibling = getSibling.call(sibling);
                        }

                        if (sibling[0] != this.placeholder[0]) {
                            this._movePlaceholder(target, direction, eventData);
                        }
                    }
                }
            },

            _dragend: function (e) {
                var placeholder = this.placeholder,
                draggedElement = this.draggedElement,
                draggedIndex = this.indexOf(draggedElement),
                placeholderIndex = this.indexOf(placeholder),
                connectWith = this.options.connectWith,
                connectedList,
                isDefaultPrevented,
                eventData,
                connectedListEventData;

                this._resetCursor();

                eventData = {
                    action: ACTION_SORT,
                    item: draggedElement,
                    oldIndex: draggedIndex,
                    newIndex: placeholderIndex,
                    draggableEvent: e
                };

                if (placeholderIndex >= 0) {
                    isDefaultPrevented = this.trigger(END, eventData);
                } else {
                    connectedList = placeholder.parents(connectWith).getKendoSortable();

                    eventData.action = ACTION_REMOVE;
                    connectedListEventData = $.extend({}, eventData, {
                        action: ACTION_RECEIVE,
                        oldIndex: MISSING_INDEX,
                        newIndex: connectedList.indexOf(placeholder)
                    });

                    isDefaultPrevented = !(!this.trigger(END, eventData) && !connectedList.trigger(END, connectedListEventData));
                }

                if (isDefaultPrevented || placeholderIndex === draggedIndex) {
                    this._cancel();
                    return;
                }

                placeholder.replaceWith(draggedElement);

                draggedElement.show();
                this._draggable.dropped = true;

                eventData = {
                    action: this.indexOf(draggedElement) != MISSING_INDEX ? ACTION_SORT : ACTION_REMOVE,
                    item: draggedElement,
                    oldIndex: draggedIndex,
                    newIndex: this.indexOf(draggedElement),
                    draggableEvent: e
                };

                this.trigger(CHANGE, eventData);

                if (connectedList) {
                    connectedListEventData = $.extend({}, eventData, {
                        action: ACTION_RECEIVE,
                        oldIndex: MISSING_INDEX,
                        newIndex: connectedList.indexOf(draggedElement)
                    });

                    connectedList.trigger(CHANGE, connectedListEventData);
                }

            },

            _findTarget: function (e) {
                var element = this._findElementUnderCursor(e),
                items,
                connectWith = this.options.connectWith,
                node;

                if ($.contains(this.element[0], element)) { //the element is part of the sortable container
                    items = this.items();
                    node = items.filter(element)[0] || items.has(element)[0];

                    return node ? { element: $(node), sortable: this} : null;
                } else if (this.element[0] == element && this._isEmpty()) {
                    return { element: this.element, sortable: this, appendToBottom: true };
                } else if (this.element[0] == element && this._isLastHidden()) {
                    node = this.items().eq(0);
                    return { element: node, sortable: this, appendAfterHidden: true };
                } else if (connectWith) { //connected lists are present
                    return this._searchConnectedTargets(element, e);
                }
            },

            _findElementUnderCursor: function (e) {
                var elementUnderCursor = kendo.elementUnderCursor(e),
                draggable = e.sender,
                disabled = this.options.disabled,
                filter = this.options.filter,
                items = this.items();

                if (containsOrEqualTo(draggable.hint[0], elementUnderCursor)) {
                    draggable.hint.hide();
                    elementUnderCursor = kendo.elementUnderCursor(e);
                    // IE8 does not return the element in iframe from first attempt
                    if (!elementUnderCursor) {
                        elementUnderCursor = kendo.elementUnderCursor(e);
                    }
                    draggable.hint.show();
                }

                return elementUnderCursor;
            },

            _searchConnectedTargets: function (element, e) {
                var connected = $(this.options.connectWith),
                sortableInstance,
                items,
                node;

                for (var i = 0; i < connected.length; i++) {
                    sortableInstance = connected.eq(i).getKendoSortable();

                    if ($.contains(connected[i], element)) {
                        if (sortableInstance) {
                            items = sortableInstance.items();
                            node = items.filter(element)[0] || items.has(element)[0];

                            if (node) {
                                sortableInstance.placeholder = this.placeholder;
                                return { element: $(node), sortable: sortableInstance };
                            } else {
                                return null;
                            }
                        }
                    } else if (connected[i] == element) {
                        if (sortableInstance && sortableInstance._isEmpty()) {
                            return { element: connected.eq(i), sortable: sortableInstance, appendToBottom: true };
                        } else if (this._isCursorAfterLast(sortableInstance, e)) {
                            node = sortableInstance.items().last();
                            return { element: node, sortable: sortableInstance };
                        }
                    }
                }

            },

            _isCursorAfterLast: function (sortable, e) {
                var lastItem = sortable.items().last(),
                cursorOffset = { left: e.x.location, top: e.y.location },
                lastItemOffset,
                delta;

                lastItemOffset = kendo.getOffset(lastItem);
                lastItemOffset.top += lastItem.outerHeight();
                lastItemOffset.left += lastItem.outerWidth();

                if (this._isFloating(lastItem)) { //horizontal
                    delta = lastItemOffset.left - cursorOffset.left;
                } else { //vertical
                    delta = lastItemOffset.top - cursorOffset.top;
                }

                return delta < 0 ? true : false;
            },

            _movementByAxis: function (axis, cursorOffset, delta, eventData) {
                var cursorPosition = (axis === "x") ? cursorOffset.left : cursorOffset.top,
                target = (delta < 0) ? this.placeholder.prev() : this.placeholder.next(),
                targetCenter;

                if (target.length && !target.is(":visible")) {
                    target = (delta < 0) ? target.prev() : target.next();
                }

                $.extend(eventData, { target: target });
                targetCenter = this._getElementCenter(target);

                if (targetCenter) {
                    targetCenter = (axis === "x") ? targetCenter.left : targetCenter.top;
                }

                if (target.length && delta < 0 && cursorPosition - targetCenter < 0) { //prev
                    this._movePlaceholder({ element: target, sortable: this }, "prev", eventData);
                } else if (target.length && delta > 0 && cursorPosition - targetCenter > 0) { //next
                    this._movePlaceholder({ element: target, sortable: this }, "next", eventData);
                }
            },

            _movePlaceholder: function (target, direction, eventData) {
                var placeholder = this.placeholder;

                if (!target.sortable.trigger(BEFORE_MOVE, eventData)) {

                    if (!direction) {
                        target.element.append(placeholder);
                    } else if (direction === "prev") {
                        target.element.before(placeholder);
                    } else if (direction === "next") {
                        target.element.after(placeholder);
                    }

                    target.sortable.trigger(MOVE, eventData);
                }
            },

            _setCursor: function () {
                var cursor = this.options.cursor,
                body;

                if (cursor && cursor !== "auto") {
                    body = $(document.body);

                    this._originalCursorType = body.css("cursor");
                    body.css({ "cursor": cursor });

                    if (!this._cursorStylesheet) {
                        this._cursorStylesheet = $("<style>* { cursor: " + cursor + " !important; }</style>");
                    }

                    this._cursorStylesheet.appendTo(body);
                }
            },

            _resetCursor: function () {
                if (this._originalCursorType) {
                    $(document.body).css("cursor", this._originalCursorType);
                    this._originalCursorType = null;

                    this._cursorStylesheet.remove();
                }
            },

            _getElementCenter: function (element) {
                var center = element.length ? kendo.getOffset(element) : null;
                if (center) {
                    center.top += element.outerHeight() / 2;
                    center.left += element.outerWidth() / 2;
                }

                return center;
            },

            _isFloating: function (item) {
                return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
            },

            _cancel: function () {
                this.draggedElement.show();
                this.placeholder.remove();
            },

            _items: function () {
                var filter = this.options.filter,
                items;

                if (filter) {
                    items = this.element.find(filter);
                } else {
                    items = this.element.children();
                }

                return items;
            },

            indexOf: function (element) {
                var items = this._items(),
                placeholder = this.placeholder,
                draggedElement = this.draggedElement;

                if (placeholder && element[0] == placeholder[0]) {
                    return items.not(draggedElement).index(element);
                } else {
                    return items.not(placeholder).index(element);
                }
            },

            items: function () {
                var placeholder = this.placeholder,
                items = this._items();

                if (placeholder) {
                    items = items.not(placeholder);
                }

                return items;
            },

            _isEmpty: function () {
                return !this.items().length;
            },

            _isLastHidden: function () {
                return this.items().length === 1 && this.items().is(":hidden");
            }

        });

        kendo.ui.plugin(Sortable);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        proxy = $.proxy,
        abs = Math.abs,
        shift = Array.prototype.shift,
        ARIASELECTED = "aria-selected",
        SELECTED = "k-state-selected",
        ACTIVE = "k-state-selecting",
        SELECTABLE = "k-selectable",
        CHANGE = "change",
        NS = ".kendoSelectable",
        UNSELECTING = "k-state-unselecting",
        INPUTSELECTOR = "input,a,textarea,.k-multiselect-wrap,select,button",
        msie = kendo.support.browser.msie,
        supportEventDelegation = false;

        (function ($) {
            (function () {
                $('<div class="parent"><span /></div>')
                .on("click", ">*", function () {
                    supportEventDelegation = true;
                })
                .find("span")
                .click()
                .end()
                .off();
            })();
        })($);

        var Selectable = Widget.extend({
            init: function (element, options) {
                var that = this,
                multiple;

                Widget.fn.init.call(that, element, options);

                that._marquee = $("<div class='k-marquee'><div class='k-marquee-color'></div></div>");
                that._lastActive = null;
                that.element.addClass(SELECTABLE);

                that.relatedTarget = that.options.relatedTarget;

                multiple = that.options.multiple;

                if (this.options.aria && multiple) {
                    that.element.attr("aria-multiselectable", true);
                }

                that.userEvents = new kendo.UserEvents(that.element, {
                    global: true,
                    allowSelection: true,
                    filter: (!supportEventDelegation ? "." + SELECTABLE + " " : "") + that.options.filter,
                    tap: proxy(that._tap, that)
                });

                if (multiple) {
                    that.userEvents
                   .bind("start", proxy(that._start, that))
                   .bind("move", proxy(that._move, that))
                   .bind("end", proxy(that._end, that))
                   .bind("select", proxy(that._select, that));
                }
            },

            events: [CHANGE],

            options: {
                name: "Selectable",
                filter: ">*",
                multiple: false,
                relatedTarget: $.noop
            },

            _isElement: function (target) {
                var elements = this.element;
                var idx, length = elements.length, result = false;

                target = target[0];

                for (idx = 0; idx < length; idx++) {
                    if (elements[idx] === target) {
                        result = true;
                        break;
                    }
                }

                return result;
            },

            _tap: function (e) {
                var target = $(e.target),
                that = this,
                ctrlKey = e.event.ctrlKey || e.event.metaKey,
                multiple = that.options.multiple,
                shiftKey = multiple && e.event.shiftKey,
                selected,
                whichCode = e.event.which,
                buttonCode = e.event.button;

                //in case of hierarchy or right-click
                if (!that._isElement(target.closest("." + SELECTABLE)) || whichCode && whichCode == 3 || buttonCode && buttonCode == 2) {
                    return;
                }

                if (!this._allowSelection(e.event.target)) {
                    return;
                }

                selected = target.hasClass(SELECTED);
                if (!multiple || !ctrlKey) {
                    that.clear();
                }

                target = target.add(that.relatedTarget(target));

                if (shiftKey) {
                    that.selectRange(that._firstSelectee(), target);
                } else {
                    if (selected && ctrlKey) {
                        that._unselect(target);
                        that._notify(CHANGE);
                    } else {
                        that.value(target);
                    }

                    that._lastActive = that._downTarget = target;
                }
            },

            _start: function (e) {
                var that = this,
                target = $(e.target),
                selected = target.hasClass(SELECTED),
                currentElement,
                ctrlKey = e.event.ctrlKey || e.event.metaKey;

                if (!this._allowSelection(e.event.target)) {
                    return;
                }

                that._downTarget = target;

                //in case of hierarchy
                if (!that._isElement(target.closest("." + SELECTABLE))) {
                    that.userEvents.cancel();
                    return;
                }

                if (that.options.useAllItems) {
                    that._items = that.element.find(that.options.filter);
                } else {
                    currentElement = target.closest(that.element);
                    that._items = currentElement.find(that.options.filter);
                }

                e.sender.capture();

                that._marquee
                .appendTo(document.body)
                .css({
                    left: e.x.client + 1,
                    top: e.y.client + 1,
                    width: 0,
                    height: 0
                });

                if (!ctrlKey) {
                    that.clear();
                }

                target = target.add(that.relatedTarget(target));
                if (selected) {
                    that._selectElement(target, true);
                    if (ctrlKey) {
                        target.addClass(UNSELECTING);
                    }
                }
            },

            _move: function (e) {
                var that = this,
                position = {
                    left: e.x.startLocation > e.x.location ? e.x.location : e.x.startLocation,
                    top: e.y.startLocation > e.y.location ? e.y.location : e.y.startLocation,
                    width: abs(e.x.initialDelta),
                    height: abs(e.y.initialDelta)
                };

                that._marquee.css(position);

                that._invalidateSelectables(position, (e.event.ctrlKey || e.event.metaKey));

                e.preventDefault();
            },

            _end: function () {
                var that = this;

                that._marquee.remove();

                that._unselect(that.element
                .find(that.options.filter + "." + UNSELECTING))
                .removeClass(UNSELECTING);


                var target = that.element.find(that.options.filter + "." + ACTIVE);
                target = target.add(that.relatedTarget(target));

                that.value(target);
                that._lastActive = that._downTarget;
                that._items = null;
            },

            _invalidateSelectables: function (position, ctrlKey) {
                var idx,
                length,
                target = this._downTarget[0],
                items = this._items,
                related,
                toSelect;

                for (idx = 0, length = items.length; idx < length; idx++) {
                    toSelect = items.eq(idx);
                    related = toSelect.add(this.relatedTarget(toSelect));

                    if (collision(toSelect, position)) {
                        if (toSelect.hasClass(SELECTED)) {
                            if (ctrlKey && target !== toSelect[0]) {
                                related.removeClass(SELECTED).addClass(UNSELECTING);
                            }
                        } else if (!toSelect.hasClass(ACTIVE) && !toSelect.hasClass(UNSELECTING)) {
                            related.addClass(ACTIVE);
                        }
                    } else {
                        if (toSelect.hasClass(ACTIVE)) {
                            related.removeClass(ACTIVE);
                        } else if (ctrlKey && toSelect.hasClass(UNSELECTING)) {
                            related.removeClass(UNSELECTING).addClass(SELECTED);
                        }
                    }
                }
            },

            value: function (val) {
                var that = this,
                selectElement = proxy(that._selectElement, that);

                if (val) {
                    val.each(function () {
                        selectElement(this);
                    });

                    that._notify(CHANGE);
                    return;
                }

                return that.element.find(that.options.filter + "." + SELECTED);
            },

            _firstSelectee: function () {
                var that = this,
                selected;

                if (that._lastActive !== null) {
                    return that._lastActive;
                }

                selected = that.value();
                return selected.length > 0 ?
                    selected[0] :
                    that.element.find(that.options.filter)[0];
            },

            _selectElement: function (element, preventNotify) {
                var toSelect = $(element),
                isPrevented = !preventNotify && this._notify("select", { element: element });

                toSelect.removeClass(ACTIVE);
                if (!isPrevented) {
                    toSelect.addClass(SELECTED);

                    if (this.options.aria) {
                        toSelect.attr(ARIASELECTED, true);
                    }
                }
            },

            _notify: function (name, args) {
                args = args || {};
                return this.trigger(name, args);
            },

            _unselect: function (element) {
                element.removeClass(SELECTED);

                if (this.options.aria) {
                    element.attr(ARIASELECTED, false);
                }

                return element;
            },

            _select: function (e) {
                if (this._allowSelection(e.event.target)) {
                    if (!msie || (msie && !$(kendo._activeElement()).is(INPUTSELECTOR))) {
                        e.preventDefault();
                    }
                }
            },

            _allowSelection: function (target) {
                if ($(target).is(INPUTSELECTOR)) {
                    this.userEvents.cancel();
                    this._downTarget = null;
                    return false;
                }

                return true;
            },

            resetTouchEvents: function () {
                this.userEvents.cancel();
            },

            clear: function () {
                var items = this.element.find(this.options.filter + "." + SELECTED);
                this._unselect(items);
            },

            selectRange: function (start, end) {
                var that = this,
                idx,
                tmp,
                items;

                that.clear();

                if (that.element.length > 1) {
                    items = that.options.continuousItems();
                }

                if (!items || !items.length) {
                    items = that.element.find(that.options.filter);
                }

                start = $.inArray($(start)[0], items);
                end = $.inArray($(end)[0], items);

                if (start > end) {
                    tmp = start;
                    start = end;
                    end = tmp;
                }

                if (!that.options.useAllItems) {
                    end += that.element.length - 1;
                }

                for (idx = start; idx <= end; idx++) {
                    that._selectElement(items[idx]);
                }

                that._notify(CHANGE);
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.element.off(NS);

                that.userEvents.destroy();

                that._marquee = that._lastActive = that.element = that.userEvents = null;
            }
        });

        Selectable.parseOptions = function (selectable) {
            var asLowerString = typeof selectable === "string" && selectable.toLowerCase();

            return {
                multiple: asLowerString && asLowerString.indexOf("multiple") > -1,
                cell: asLowerString && asLowerString.indexOf("cell") > -1
            };
        };

        function collision(element, position) {
            var elementPosition = kendo.getOffset(element),
            right = position.left + position.width,
            bottom = position.top + position.height;

            elementPosition.right = elementPosition.left + element.outerWidth();
            elementPosition.bottom = elementPosition.top + element.outerHeight();

            return !(elementPosition.left > right ||
            elementPosition.right < position.left ||
            elementPosition.top > bottom ||
            elementPosition.bottom < position.top);
        }

        kendo.ui.plugin(Selectable);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        proxy = $.proxy,
        keys = kendo.keys,
        CLICK = "click",
        KBUTTON = "k-button",
        KBUTTONICON = "k-button-icon",
        KBUTTONICONTEXT = "k-button-icontext",
        NS = ".kendoButton",
        DISABLED = "disabled",
        DISABLEDSTATE = "k-state-disabled",
        FOCUSEDSTATE = "k-state-focused",
        SELECTEDSTATE = "k-state-selected";

        var Button = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                element = that.wrapper = that.element;
                options = that.options;

                element.addClass(KBUTTON).attr("role", "button");

                options.enable = options.enable && !element.attr(DISABLED);
                that.enable(options.enable);

                that._tabindex();

                that._graphics();

                element
                .on(CLICK + NS, proxy(that._click, that))
                .on("focus" + NS, proxy(that._focus, that))
                .on("blur" + NS, proxy(that._blur, that))
                .on("keydown" + NS, proxy(that._keydown, that))
                .on("keyup" + NS, proxy(that._keyup, that));

                kendo.notify(that);
            },

            destroy: function () {
                var that = this;

                that.wrapper.off(NS);

                Widget.fn.destroy.call(that);
            },

            events: [
            CLICK
        ],

            options: {
                name: "Button",
                icon: "",
                spriteCssClass: "",
                imageUrl: "",
                enable: true
            },

            _isNativeButton: function () {
                return this.element.prop("tagName").toLowerCase() == "button";
            },

            _click: function (e) {
                if (this.options.enable) {
                    this.trigger(CLICK, { event: e });
                }
            },

            _focus: function () {
                if (this.options.enable) {
                    this.element.addClass(FOCUSEDSTATE);
                }
            },

            _blur: function () {
                this.element.removeClass(FOCUSEDSTATE);
            },

            _keydown: function (e) {
                var that = this;
                if (!that._isNativeButton()) {
                    if (e.keyCode == keys.ENTER || e.keyCode == keys.SPACEBAR) {
                        if (e.keyCode == keys.SPACEBAR) {
                            e.preventDefault();
                            if (that.options.enable) {
                                that.element.addClass(SELECTEDSTATE);
                            }
                        }
                        that._click(e);
                    }
                }
            },

            _keyup: function () {
                this.element.removeClass(SELECTEDSTATE);
            },

            _graphics: function () {
                var that = this,
                element = that.element,
                options = that.options,
                icon = options.icon,
                spriteCssClass = options.spriteCssClass,
                imageUrl = options.imageUrl,
                span, img, isEmpty;

                if (spriteCssClass || imageUrl || icon) {
                    isEmpty = true;

                    element.contents().not("span.k-sprite").not("span.k-icon").not("img.k-image").each(function (idx, el) {
                        if (el.nodeType == 1 || el.nodeType == 3 && $.trim(el.nodeValue).length > 0) {
                            isEmpty = false;
                        }
                    });

                    if (isEmpty) {
                        element.addClass(KBUTTONICON);
                    } else {
                        element.addClass(KBUTTONICONTEXT);
                    }
                }

                if (icon) {
                    span = element.children("span.k-icon").first();
                    if (!span[0]) {
                        span = $('<span class="k-icon"></span>').prependTo(element);
                    }
                    span.addClass("k-i-" + icon);
                } else if (spriteCssClass) {
                    span = element.children("span.k-sprite").first();
                    if (!span[0]) {
                        span = $('<span class="k-sprite"></span>').prependTo(element);
                    }
                    span.addClass(spriteCssClass);
                } else if (imageUrl) {
                    img = element.children("img.k-image").first();
                    if (!img[0]) {
                        img = $('<img alt="icon" class="k-image" />').prependTo(element);
                    }
                    img.attr("src", imageUrl);
                }
            },

            enable: function (enable) {
                var that = this,
                element = that.element;

                if (enable === undefined) {
                    enable = true;
                }

                enable = !!enable;
                that.options.enable = enable;
                element.toggleClass(DISABLEDSTATE, !enable)
                   .attr("aria-disabled", !enable)
                   .attr(DISABLED, !enable);
                // prevent 'Unspecified error' in IE when inside iframe
                try {
                    element.blur();
                } catch (err) {
                }
            }
        });

        kendo.ui.plugin(Button);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        proxy = $.proxy,
        FIRST = ".k-i-seek-w",
        LAST = ".k-i-seek-e",
        PREV = ".k-i-arrow-w",
        NEXT = ".k-i-arrow-e",
        CHANGE = "change",
        NS = ".kendoPager",
        CLICK = "click",
        KEYDOWN = "keydown",
        DISABLED = "disabled",
        iconTemplate = kendo.template('<a href="\\#" title="#=text#" class="k-link k-pager-nav #= wrapClassName #"><span class="k-icon #= className #">#=text#</span></a>');

        function button(template, idx, text, numeric, title) {
            return template({
                idx: idx,
                text: text,
                ns: kendo.ns,
                numeric: numeric,
                title: title || ""
            });
        }

        function icon(className, text, wrapClassName) {
            return iconTemplate({
                className: className.substring(1),
                text: text,
                wrapClassName: wrapClassName || ""
            });
        }

        function update(element, selector, page, disabled) {
            element.find(selector)
              .parent()
              .attr(kendo.attr("page"), page)
              .attr("tabindex", -1)
              .toggleClass("k-state-disabled", disabled);
        }

        function first(element, page) {
            update(element, FIRST, 1, page <= 1);
        }

        function prev(element, page) {
            update(element, PREV, Math.max(1, page - 1), page <= 1);
        }

        function next(element, page, totalPages) {
            update(element, NEXT, Math.min(totalPages, page + 1), page >= totalPages);
        }

        function last(element, page, totalPages) {
            update(element, LAST, totalPages, page >= totalPages);
        }

        var Pager = Widget.extend({
            init: function (element, options) {
                var that = this, page, totalPages;

                Widget.fn.init.call(that, element, options);

                options = that.options;
                that.dataSource = kendo.data.DataSource.create(options.dataSource);
                that.linkTemplate = kendo.template(that.options.linkTemplate);
                that.selectTemplate = kendo.template(that.options.selectTemplate);

                page = that.page();
                totalPages = that.totalPages();

                that._refreshHandler = proxy(that.refresh, that);

                that.dataSource.bind(CHANGE, that._refreshHandler);

                if (options.previousNext) {
                    if (!that.element.find(FIRST).length) {
                        that.element.append(icon(FIRST, options.messages.first, "k-pager-first"));

                        first(that.element, page, totalPages);
                    }

                    if (!that.element.find(PREV).length) {
                        that.element.append(icon(PREV, options.messages.previous));

                        prev(that.element, page, totalPages);
                    }
                }

                if (options.numeric) {
                    that.list = that.element.find(".k-pager-numbers");

                    if (!that.list.length) {
                        that.list = $('<ul class="k-pager-numbers k-reset" />').appendTo(that.element);
                    }
                }

                if (options.input) {
                    if (!that.element.find(".k-pager-input").length) {
                        that.element.append('<span class="k-pager-input k-label">' +
                       options.messages.page +
                       '<input class="k-textbox">' +
                       kendo.format(options.messages.of, totalPages) +
                       '</span>');
                    }

                    that.element.on(KEYDOWN + NS, ".k-pager-input input", proxy(that._keydown, that));
                }

                if (options.previousNext) {
                    if (!that.element.find(NEXT).length) {
                        that.element.append(icon(NEXT, options.messages.next));

                        next(that.element, page, totalPages);
                    }

                    if (!that.element.find(LAST).length) {
                        that.element.append(icon(LAST, options.messages.last, "k-pager-last"));

                        last(that.element, page, totalPages);
                    }
                }

                if (options.pageSizes) {
                    if (!that.element.find(".k-pager-sizes").length) {
                        $('<span class="k-pager-sizes k-label"><select/>' + options.messages.itemsPerPage + "</span>")
                        .appendTo(that.element)
                        .find("select")
                        .html($.map($.isArray(options.pageSizes) ? options.pageSizes : [5, 10, 20], function (page) {
                            return "<option>" + page + "</option>";
                        }).join(""))
                        .end()
                        .appendTo(that.element);
                    }

                    that.element.find(".k-pager-sizes select").val(that.pageSize());

                    if (kendo.ui.DropDownList) {
                        that.element.find(".k-pager-sizes select").show().kendoDropDownList();
                    }

                    that.element.on(CHANGE + NS, ".k-pager-sizes select", proxy(that._change, that));
                }

                if (options.refresh) {
                    if (!that.element.find(".k-pager-refresh").length) {
                        that.element.append('<a href="#" class="k-pager-refresh k-link" title="' + options.messages.refresh +
                        '"><span class="k-icon k-i-refresh">' + options.messages.refresh + "</span></a>");
                    }

                    that.element.on(CLICK + NS, ".k-pager-refresh", proxy(that._refreshClick, that));
                }

                if (options.info) {
                    if (!that.element.find(".k-pager-info").length) {
                        that.element.append('<span class="k-pager-info k-label" />');
                    }
                }

                that.element
                .on(CLICK + NS, "a", proxy(that._click, that))
                .addClass("k-pager-wrap k-widget");

                if (options.autoBind) {
                    that.refresh();
                }

                kendo.notify(that);
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.element.off(NS);
                that.dataSource.unbind(CHANGE, that._refreshHandler);
                that._refreshHandler = null;

                kendo.destroy(that.element);
                that.element = that.list = null;
            },

            events: [
            CHANGE
        ],

            options: {
                name: "Pager",
                selectTemplate: '<li><span class="k-state-selected">#=text#</span></li>',
                linkTemplate: '<li><a tabindex="-1" href="\\#" class="k-link" data-#=ns#page="#=idx#" #if (title !== "") {# title="#=title#" #}#>#=text#</a></li>',
                buttonCount: 10,
                autoBind: true,
                numeric: true,
                info: true,
                input: false,
                previousNext: true,
                pageSizes: false,
                refresh: false,
                messages: {
                    display: "{0} - {1} of {2} items",
                    empty: "No items to display",
                    page: "Page",
                    of: "of {0}",
                    itemsPerPage: "items per page",
                    first: "Go to the first page",
                    previous: "Go to the previous page",
                    next: "Go to the next page",
                    last: "Go to the last page",
                    refresh: "Refresh",
                    morePages: "More pages"
                }
            },

            setDataSource: function (dataSource) {
                var that = this;

                that.dataSource.unbind(CHANGE, that._refreshHandler);
                that.dataSource = that.options.dataSource = dataSource;
                dataSource.bind(CHANGE, that._refreshHandler);

                if (that.options.autoBind) {
                    dataSource.fetch();
                }
            },

            refresh: function (e) {
                var that = this,
                idx,
                end,
                start = 1,
                html = "",
                reminder,
                page = that.page(),
                options = that.options,
                pageSize = that.pageSize(),
                total = that.dataSource.total(),
                totalPages = that.totalPages(),
                linkTemplate = that.linkTemplate,
                buttonCount = options.buttonCount;

                if (e && e.action == "itemchange") {
                    return;
                }

                if (options.numeric) {
                    if (page > buttonCount) {
                        reminder = (page % buttonCount);

                        start = (reminder === 0) ? (page - buttonCount) + 1 : (page - reminder) + 1;
                    }

                    end = Math.min((start + buttonCount) - 1, totalPages);

                    if (start > 1) {
                        html += button(linkTemplate, start - 1, "...", false, options.messages.morePages);
                    }

                    for (idx = start; idx <= end; idx++) {
                        html += button(idx == page ? that.selectTemplate : linkTemplate, idx, idx, true);
                    }

                    if (end < totalPages) {
                        html += button(linkTemplate, idx, "...", false, options.messages.morePages);
                    }

                    if (html === "") {
                        html = that.selectTemplate({ text: 0 });
                    }

                    that.list.html(html);
                }

                if (options.info) {
                    if (total > 0) {
                        html = kendo.format(options.messages.display,
                        (page - 1) * pageSize + 1, // first item in the page
                        Math.min(page * pageSize, total), // last item in the page
                    total);
                    } else {
                        html = options.messages.empty;
                    }

                    that.element.find(".k-pager-info").html(html);
                }

                if (options.input) {
                    that.element
                    .find(".k-pager-input")
                    .html(that.options.messages.page +
                        '<input class="k-textbox">' +
                        kendo.format(options.messages.of, totalPages))
                    .find("input")
                    .val(page)
                    .attr(DISABLED, total < 1)
                    .toggleClass("k-state-disabled", total < 1);
                }

                if (options.previousNext) {
                    first(that.element, page, totalPages);

                    prev(that.element, page, totalPages);

                    next(that.element, page, totalPages);

                    last(that.element, page, totalPages);
                }

                if (options.pageSizes) {
                    that.element
                    .find(".k-pager-sizes select")
                    .val(pageSize)
                    .filter("[" + kendo.attr("role") + "=dropdownlist]")
                    .kendoDropDownList("value", pageSize)
                    .kendoDropDownList("text", pageSize); // handles custom values
                }
            },

            _keydown: function (e) {
                if (e.keyCode === kendo.keys.ENTER) {
                    var input = this.element.find(".k-pager-input").find("input"),
                    page = parseInt(input.val(), 10);

                    if (isNaN(page) || page < 1 || page > this.totalPages()) {
                        page = this.page();
                    }

                    input.val(page);

                    this.page(page);
                }
            },

            _refreshClick: function (e) {
                e.preventDefault();

                this.dataSource.read();
            },

            _change: function (e) {
                var pageSize = parseInt(e.currentTarget.value, 10);

                if (!isNaN(pageSize)) {
                    this.dataSource.pageSize(pageSize);
                }
            },

            _click: function (e) {
                var target = $(e.currentTarget);

                e.preventDefault();

                if (!target.is(".k-state-disabled")) {
                    this.page(target.attr(kendo.attr("page")));
                }
            },

            totalPages: function () {
                return Math.ceil((this.dataSource.total() || 0) / this.pageSize());
            },

            pageSize: function () {
                return this.dataSource.pageSize() || this.dataSource.total();
            },

            page: function (page) {
                if (page !== undefined) {
                    this.dataSource.page(page);

                    this.trigger(CHANGE, { index: page });
                } else {
                    if (this.dataSource.total() > 0) {
                        return this.dataSource.page();
                    } else {
                        return 0;
                    }
                }
            }
        });

        ui.plugin(Pager);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        support = kendo.support,
        getOffset = kendo.getOffset,
        activeElement = kendo._activeElement,
        OPEN = "open",
        CLOSE = "close",
        DEACTIVATE = "deactivate",
        ACTIVATE = "activate",
        CENTER = "center",
        LEFT = "left",
        RIGHT = "right",
        TOP = "top",
        BOTTOM = "bottom",
        ABSOLUTE = "absolute",
        HIDDEN = "hidden",
        BODY = "body",
        LOCATION = "location",
        POSITION = "position",
        VISIBLE = "visible",
        EFFECTS = "effects",
        ACTIVE = "k-state-active",
        ACTIVEBORDER = "k-state-border",
        ACTIVEBORDERREGEXP = /k-state-border-(\w+)/,
        ACTIVECHILDREN = ".k-picker-wrap, .k-dropdown-wrap, .k-link",
        MOUSEDOWN = "down",
        DOCUMENT_ELEMENT = $(document.documentElement),
        WINDOW = $(window),
        SCROLL = "scroll",
        RESIZE_SCROLL = "resize scroll",
        cssPrefix = support.transitions.css,
        TRANSFORM = cssPrefix + "transform",
        extend = $.extend,
        NS = ".kendoPopup",
        styles = ["font-size",
                  "font-family",
                  "font-stretch",
                  "font-style",
                  "font-weight",
                  "line-height"];

        function contains(container, target) {
            return container === target || $.contains(container, target);
        }

        var Popup = Widget.extend({
            init: function (element, options) {
                var that = this, parentPopup;

                options = options || {};

                if (options.isRtl) {
                    options.origin = options.origin || BOTTOM + " " + RIGHT;
                    options.position = options.position || TOP + " " + RIGHT;
                }

                Widget.fn.init.call(that, element, options);

                element = that.element;
                options = that.options;

                that.collisions = options.collision ? options.collision.split(" ") : [];
                that.downEvent = kendo.applyEventMap(MOUSEDOWN, kendo.guid());

                if (that.collisions.length === 1) {
                    that.collisions.push(that.collisions[0]);
                }

                parentPopup = $(that.options.anchor).closest(".k-popup,.k-group").filter(":not([class^=km-])"); // When popup is in another popup, make it relative.
                options.appendTo = $($(options.appendTo)[0] || parentPopup[0] || BODY);

                that.element.hide()
                .addClass("k-popup k-group k-reset")
                .toggleClass("k-rtl", !!options.isRtl)
                .css({ position: ABSOLUTE })
                .appendTo(options.appendTo)
                .on("mouseenter" + NS, function () {
                    that._hovered = true;
                })
                .on("mouseleave" + NS, function () {
                    that._hovered = false;
                });

                that.wrapper = $();

                if (options.animation === false) {
                    options.animation = { open: { effects: {} }, close: { hide: true, effects: {}} };
                }

                extend(options.animation.open, {
                    complete: function () {
                        that.wrapper.css({ overflow: VISIBLE }); // Forcing refresh causes flickering in mobile.
                        that._trigger(ACTIVATE);
                    }
                });

                extend(options.animation.close, {
                    complete: function () {
                        that._animationClose();
                    }
                });

                that._mousedownProxy = function (e) {
                    that._mousedown(e);
                };

                that._resizeProxy = function (e) {
                    that._resize(e);
                };

                if (options.toggleTarget) {
                    $(options.toggleTarget).on(options.toggleEvent + NS, $.proxy(that.toggle, that));
                }
            },

            events: [
            OPEN,
            ACTIVATE,
            CLOSE,
            DEACTIVATE
        ],

            options: {
                name: "Popup",
                toggleEvent: "click",
                origin: BOTTOM + " " + LEFT,
                position: TOP + " " + LEFT,
                anchor: BODY,
                appendTo: null,
                collision: "flip fit",
                viewport: window,
                copyAnchorStyles: true,
                autosize: false,
                modal: false,
                animation: {
                    open: {
                        effects: "slideIn:down",
                        transition: true,
                        duration: 200
                    },
                    close: { // if close animation effects are defined, they will be used instead of open.reverse
                        duration: 100,
                        hide: true
                    }
                }
            },

            _animationClose: function () {
                var that = this,
                options = that.options;

                that.wrapper.hide();

                var location = that.wrapper.data(LOCATION),
                anchor = $(options.anchor),
                direction, dirClass;

                if (location) {
                    that.wrapper.css(location);
                }

                if (options.anchor != BODY) {
                    direction = ((anchor.attr("class") || "").match(ACTIVEBORDERREGEXP) || ["", "down"])[1];
                    dirClass = ACTIVEBORDER + "-" + direction;

                    anchor
                    .removeClass(dirClass)
                    .children(ACTIVECHILDREN)
                    .removeClass(ACTIVE)
                    .removeClass(dirClass);

                    that.element.removeClass(ACTIVEBORDER + "-" + kendo.directions[direction].reverse);
                }

                that._closing = false;
                that._trigger(DEACTIVATE);
            },

            destroy: function () {
                var that = this,
                options = that.options,
                element = that.element.off(NS),
                parent;

                Widget.fn.destroy.call(that);

                if (options.toggleTarget) {
                    $(options.toggleTarget).off(NS);
                }

                if (!options.modal) {
                    DOCUMENT_ELEMENT.unbind(that.downEvent, that._mousedownProxy);
                    that._scrollableParents().unbind(SCROLL, that._resizeProxy);
                    WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy);
                }

                kendo.destroy(that.element.children());
                element.removeData();

                if (options.appendTo[0] === document.body) {
                    parent = element.parent(".k-animation-container");

                    if (parent[0]) {
                        parent.remove();
                    } else {
                        element.remove();
                    }
                }
            },

            open: function (x, y) {
                var that = this,
                fixed = { isFixed: !isNaN(parseInt(y, 10)), x: x, y: y },
                element = that.element,
                options = that.options,
                direction = "down",
                animation, wrapper,
                anchor = $(options.anchor),
                mobile = element[0] && element.hasClass("km-widget");

                if (!that.visible()) {
                    if (options.copyAnchorStyles) {
                        if (mobile && styles[0] == "font-size") {
                            styles.shift();
                        }
                        element.css(kendo.getComputedStyles(anchor[0], styles));
                    }

                    if (element.data("animating") || that._trigger(OPEN)) {
                        return;
                    }

                    if (!options.modal) {
                        DOCUMENT_ELEMENT.unbind(that.downEvent, that._mousedownProxy)
                                .bind(that.downEvent, that._mousedownProxy);

                        // this binding hangs iOS in editor
                        if (!(support.mobileOS.ios || support.mobileOS.android)) {
                            // all elements in IE7/8 fire resize event, causing mayhem
                            that._scrollableParents()
                            .unbind(SCROLL, that._resizeProxy)
                            .bind(SCROLL, that._resizeProxy);
                            WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy)
                              .bind(RESIZE_SCROLL, that._resizeProxy);
                        }
                    }

                    that.wrapper = wrapper = kendo.wrap(element, options.autosize)
                                        .css({
                                            overflow: HIDDEN,
                                            display: "block",
                                            position: ABSOLUTE
                                        });

                    if (support.mobileOS.android) {
                        wrapper.css(TRANSFORM, "translatez(0)"); // Android is VERY slow otherwise. Should be tested in other droids as well since it may cause blur.
                    }

                    wrapper.css(POSITION);

                    if ($(options.appendTo)[0] == document.body) {
                        wrapper.css(TOP, "-10000px");
                    }

                    animation = extend(true, {}, options.animation.open);
                    that.flipped = that._position(fixed);
                    animation.effects = kendo.parseEffects(animation.effects, that.flipped);

                    direction = animation.effects.slideIn ? animation.effects.slideIn.direction : direction;

                    if (options.anchor != BODY) {
                        var dirClass = ACTIVEBORDER + "-" + direction;

                        element.addClass(ACTIVEBORDER + "-" + kendo.directions[direction].reverse);

                        anchor
                        .addClass(dirClass)
                        .children(ACTIVECHILDREN)
                        .addClass(ACTIVE)
                        .addClass(dirClass);
                    }

                    element.data(EFFECTS, animation.effects)
                       .kendoStop(true)
                       .kendoAnimate(animation);
                }
            },

            toggle: function () {
                var that = this;

                that[that.visible() ? CLOSE : OPEN]();
            },

            visible: function () {
                return this.element.is(":" + VISIBLE);
            },

            close: function (skipEffects) {
                var that = this,
                options = that.options, wrap,
                animation, openEffects, closeEffects;

                if (that.visible()) {
                    wrap = (that.wrapper[0] ? that.wrapper : kendo.wrap(that.element).hide());

                    if (that._closing || that._trigger(CLOSE)) {
                        return;
                    }

                    // Close all inclusive popups.
                    that.element.find(".k-popup").each(function () {
                        var that = $(this),
                        popup = that.data("kendoPopup");

                        if (popup) {
                            popup.close(skipEffects);
                        }
                    });

                    DOCUMENT_ELEMENT.unbind(that.downEvent, that._mousedownProxy);
                    that._scrollableParents().unbind(SCROLL, that._resizeProxy);
                    WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy);

                    if (skipEffects) {
                        animation = { hide: true, effects: {} };
                    } else {
                        animation = extend(true, {}, options.animation.close);
                        openEffects = that.element.data(EFFECTS);
                        closeEffects = animation.effects;

                        if (!closeEffects && !kendo.size(closeEffects) && openEffects && kendo.size(openEffects)) {
                            animation.effects = openEffects;
                            animation.reverse = true;
                        }

                        that._closing = true;
                    }

                    that.element.kendoStop(true);
                    wrap.css({ overflow: HIDDEN }); // stop callback will remove hidden overflow
                    that.element.kendoAnimate(animation);
                }
            },

            _trigger: function (ev) {
                return this.trigger(ev, { type: ev });
            },

            _resize: function (e) {
                var that = this;

                if (e.type === "resize") {
                    clearTimeout(that._resizeTimeout);
                    that._resizeTimeout = setTimeout(function () {
                        that._position();
                        that._resizeTimeout = null;
                    }, 50);
                } else {
                    if (!that._hovered && !contains(that.element[0], activeElement())) {
                        that.close();
                    }
                }
            },

            _mousedown: function (e) {
                var that = this,
                container = that.element[0],
                options = that.options,
                anchor = $(options.anchor)[0],
                toggleTarget = options.toggleTarget,
                target = kendo.eventTarget(e),
                popup = $(target).closest(".k-popup"),
                mobile = popup.parent().parent(".km-shim").length;

                popup = popup[0];
                if (!mobile && popup && popup !== that.element[0]) {
                    return;
                }

                // This MAY result in popup not closing in certain cases.
                if ($(e.target).closest("a").data("rel") === "popover") {
                    return;
                }

                if (!contains(container, target) && !contains(anchor, target) && !(toggleTarget && contains($(toggleTarget)[0], target))) {
                    that.close();
                }
            },

            _fit: function (position, size, viewPortSize) {
                var output = 0;

                if (position + size > viewPortSize) {
                    output = viewPortSize - (position + size);
                }

                if (position < 0) {
                    output = -position;
                }

                return output;
            },

            _flip: function (offset, size, anchorSize, viewPortSize, origin, position, boxSize) {
                var output = 0;
                boxSize = boxSize || size;

                if (position !== origin && position !== CENTER && origin !== CENTER) {
                    if (offset + boxSize > viewPortSize) {
                        output += -(anchorSize + size);
                    }

                    if (offset + output < 0) {
                        output += anchorSize + size;
                    }
                }
                return output;
            },

            _scrollableParents: function () {
                return $(this.options.anchor)
                       .parentsUntil("body")
                       .filter(function (index, element) {
                           var computedStyle = kendo.getComputedStyles(element, ["overflow"]);
                           return computedStyle.overflow != "visible";
                       });
            },

            _position: function (fixed) {
                var that = this,
                element = that.element.css(POSITION, ""),
                wrapper = that.wrapper,
                options = that.options,
                viewport = $(options.viewport),
                viewportOffset = viewport.offset(),
                anchor = $(options.anchor),
                origins = options.origin.toLowerCase().split(" "),
                positions = options.position.toLowerCase().split(" "),
                collisions = that.collisions,
                zoomLevel = support.zoomLevel(),
                siblingContainer, parents,
                parentZIndex, zIndex = 10002,
                isWindow = !!((viewport[0] == window) && window.innerWidth && (zoomLevel <= 1.02)),
                idx = 0, length, viewportWidth, viewportHeight;

                // $(window).height() uses documentElement to get the height
                viewportWidth = isWindow ? window.innerWidth : viewport.width();
                viewportHeight = isWindow ? window.innerHeight : viewport.height();

                siblingContainer = anchor.parents().filter(wrapper.siblings());

                if (siblingContainer[0]) {
                    parentZIndex = Math.max(Number(siblingContainer.css("zIndex")), 0);

                    // set z-index to be more than that of the container/sibling
                    // compensate with more units for window z-stack
                    if (parentZIndex) {
                        zIndex = parentZIndex + 10;
                    } else {
                        parents = anchor.parentsUntil(siblingContainer);
                        for (length = parents.length; idx < length; idx++) {
                            parentZIndex = Number($(parents[idx]).css("zIndex"));
                            if (parentZIndex && zIndex < parentZIndex) {
                                zIndex = parentZIndex + 10;
                            }
                        }
                    }
                }

                wrapper.css("zIndex", zIndex);

                if (fixed && fixed.isFixed) {
                    wrapper.css({ left: fixed.x, top: fixed.y });
                } else {
                    wrapper.css(that._align(origins, positions));
                }

                var pos = getOffset(wrapper, POSITION, anchor[0] === wrapper.offsetParent()[0]),
                offset = getOffset(wrapper),
                anchorParent = anchor.offsetParent().parent(".k-animation-container,.k-popup,.k-group"); // If the parent is positioned, get the current positions

                if (anchorParent.length) {
                    pos = getOffset(wrapper, POSITION, true);
                    offset = getOffset(wrapper);
                }

                if (viewport[0] === window) {
                    offset.top -= (window.pageYOffset || document.documentElement.scrollTop || 0);
                    offset.left -= (window.pageXOffset || document.documentElement.scrollLeft || 0);
                }
                else {
                    offset.top -= viewportOffset.top;
                    offset.left -= viewportOffset.left;
                }

                if (!that.wrapper.data(LOCATION)) { // Needed to reset the popup location after every closure - fixes the resize bugs.
                    wrapper.data(LOCATION, extend({}, pos));
                }

                var offsets = extend({}, offset),
                location = extend({}, pos);

                if (collisions[0] === "fit") {
                    location.top += that._fit(offsets.top, wrapper.outerHeight(), viewportHeight / zoomLevel);
                }

                if (collisions[1] === "fit") {
                    location.left += that._fit(offsets.left, wrapper.outerWidth(), viewportWidth / zoomLevel);
                }

                var flipPos = extend({}, location);

                if (collisions[0] === "flip") {
                    location.top += that._flip(offsets.top, element.outerHeight(), anchor.outerHeight(), viewportHeight / zoomLevel, origins[0], positions[0], wrapper.outerHeight());
                }

                if (collisions[1] === "flip") {
                    location.left += that._flip(offsets.left, element.outerWidth(), anchor.outerWidth(), viewportWidth / zoomLevel, origins[1], positions[1], wrapper.outerWidth());
                }

                element.css(POSITION, ABSOLUTE);
                wrapper.css(location);

                return (location.left != flipPos.left || location.top != flipPos.top);
            },

            _align: function (origin, position) {
                var that = this,
                element = that.wrapper,
                anchor = $(that.options.anchor),
                verticalOrigin = origin[0],
                horizontalOrigin = origin[1],
                verticalPosition = position[0],
                horizontalPosition = position[1],
                anchorOffset = getOffset(anchor),
                appendTo = $(that.options.appendTo),
                appendToOffset,
                width = element.outerWidth(),
                height = element.outerHeight(),
                anchorWidth = anchor.outerWidth(),
                anchorHeight = anchor.outerHeight(),
                top = anchorOffset.top,
                left = anchorOffset.left,
                round = Math.round;

                if (appendTo[0] != document.body) {
                    appendToOffset = getOffset(appendTo);
                    top -= appendToOffset.top;
                    left -= appendToOffset.left;
                }


                if (verticalOrigin === BOTTOM) {
                    top += anchorHeight;
                }

                if (verticalOrigin === CENTER) {
                    top += round(anchorHeight / 2);
                }

                if (verticalPosition === BOTTOM) {
                    top -= height;
                }

                if (verticalPosition === CENTER) {
                    top -= round(height / 2);
                }

                if (horizontalOrigin === RIGHT) {
                    left += anchorWidth;
                }

                if (horizontalOrigin === CENTER) {
                    left += round(anchorWidth / 2);
                }

                if (horizontalPosition === RIGHT) {
                    left -= width;
                }

                if (horizontalPosition === CENTER) {
                    left -= round(width / 2);
                }

                return {
                    top: top,
                    left: left
                };
            }
        });

        ui.plugin(Popup);
    })(window.kendo.jQuery);



    ;

    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        proxy = $.proxy,
        extend = $.extend,
        setTimeout = window.setTimeout,
        CLICK = "click",
        SHOW = "show",
        HIDE = "hide",
        KNOTIFICATION = "k-notification",
        KICLOSE = ".k-notification-wrap .k-i-close",
        INFO = "info",
        SUCCESS = "success",
        WARNING = "warning",
        ERROR = "error",
        TOP = "top",
        LEFT = "left",
        BOTTOM = "bottom",
        RIGHT = "right",
        UP = "up",
        NS = ".kendoNotification",
        WRAPPER = '<div class="k-widget k-notification"></div>',
        TEMPLATE = '<div class="k-notification-wrap">' +
                '<span class="k-icon k-i-note">#=typeIcon#</span>' +
                '#=content#' +
                '<span class="k-icon k-i-close">Hide</span>' +
            '</div>';

        var Notification = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                options = that.options;

                if (!options.appendTo || !$(options.appendTo).is(element)) {
                    that.element.hide();
                }

                that._compileTemplates(options.templates);
                that._guid = "_" + kendo.guid();
                that._isRtl = kendo.support.isRtl(element);
                that._compileStacking(options.stacking, options.position.top);

                kendo.notify(that);
            },

            events: [
            SHOW,
            HIDE
        ],

            options: {
                name: "Notification",
                position: {
                    pinned: true,
                    top: null,
                    left: null,
                    bottom: 20,
                    right: 20
                },
                stacking: "default",
                hideOnClick: true,
                button: false,
                allowHideAfter: 0,
                autoHideAfter: 5000,
                appendTo: null,
                width: null,
                height: null,
                templates: [],
                animation: {
                    open: {
                        effects: "fade:in",
                        duration: 300
                    },
                    close: {
                        effects: "fade:out",
                        duration: 600,
                        hide: true
                    }
                }
            },

            _compileTemplates: function (templates) {
                var that = this;
                var kendoTemplate = kendo.template;

                that._compiled = {};

                $.each(templates, function (key, value) {
                    that._compiled[value.type] = kendoTemplate(value.template || $("#" + value.templateId).html());
                });

                that._defaultCompiled = kendoTemplate(TEMPLATE);
            },

            _getCompiled: function (type) {
                var that = this;
                var defaultCompiled = that._defaultCompiled;

                return type ? that._compiled[type] || defaultCompiled : defaultCompiled;
            },

            _compileStacking: function (stacking, top) {
                var that = this,
                paddings = { paddingTop: 0, paddingRight: 0, paddingBottom: 0, paddingLeft: 0 },
                origin, position;

                switch (stacking) {
                    case "down":
                        origin = BOTTOM + " " + LEFT;
                        position = TOP + " " + LEFT;
                        delete paddings.paddingBottom;
                        break;
                    case RIGHT:
                        origin = TOP + " " + RIGHT;
                        position = TOP + " " + LEFT;
                        delete paddings.paddingRight;
                        break;
                    case LEFT:
                        origin = TOP + " " + LEFT;
                        position = TOP + " " + RIGHT;
                        delete paddings.paddingLeft;
                        break;
                    case UP:
                        origin = TOP + " " + LEFT;
                        position = BOTTOM + " " + LEFT;
                        delete paddings.paddingTop;
                        break;
                    default:
                        if (top !== null) {
                            origin = BOTTOM + " " + LEFT;
                            position = TOP + " " + LEFT;
                            delete paddings.paddingBottom;
                        } else {
                            origin = TOP + " " + LEFT;
                            position = BOTTOM + " " + LEFT;
                            delete paddings.paddingTop;
                        }
                        break;
                }

                that._popupOrigin = origin;
                that._popupPosition = position;
                that._popupPaddings = paddings;
            },

            _attachPopupEvents: function (options, popup) {
                var that = this,
                allowHideAfter = options.allowHideAfter,
                attachDelay = !isNaN(allowHideAfter) && allowHideAfter > 0,
                closeIcon;

                function attachClick(target) {
                    target.on(CLICK + NS, function () {
                        popup.close();
                    });
                }

                if (options.hideOnClick) {
                    popup.bind("activate", function (e) {
                        if (attachDelay) {
                            setTimeout(function () {
                                attachClick(popup.element);
                            }, allowHideAfter);
                        } else {
                            attachClick(popup.element);
                        }
                    });
                } else if (options.button) {
                    closeIcon = popup.element.find(KICLOSE);
                    if (attachDelay) {
                        setTimeout(function () {
                            attachClick(closeIcon);
                        }, allowHideAfter);
                    } else {
                        attachClick(closeIcon);
                    }
                }
            },

            _showPopup: function (wrapper, options) {
                var that = this,
                autoHideAfter = options.autoHideAfter,
                x = options.position.left,
                y = options.position.top,
                allowHideAfter = options.allowHideAfter,
                popup, openPopup, attachClick, closeIcon;

                openPopup = $("." + that._guid).last();

                popup = new kendo.ui.Popup(wrapper, {
                    anchor: openPopup[0] ? openPopup : document.body,
                    origin: that._popupOrigin,
                    position: that._popupPosition,
                    animation: options.animation,
                    modal: true,
                    collision: "",
                    isRtl: that._isRtl,
                    close: function (e) {
                        that._triggerHide(this.element);
                    },
                    deactivate: function (e) {
                        e.sender.element.off(NS);
                        e.sender.element.find(KICLOSE).off(NS);
                        e.sender.destroy();
                    }
                });

                that._attachPopupEvents(options, popup);

                if (openPopup[0]) {
                    popup.open();
                } else {
                    if (x === null) {
                        x = $(window).width() - wrapper.width() - options.position.right;
                    }

                    if (y === null) {
                        y = $(window).height() - wrapper.height() - options.position.bottom;
                    }

                    popup.open(x, y);
                }

                popup.wrapper.addClass(that._guid).css(extend({ margin: 0 }, that._popupPaddings));

                if (options.position.pinned) {
                    popup.wrapper.css("position", "fixed");
                    if (openPopup[0]) {
                        that._togglePin(popup.wrapper, true);
                    }
                } else if (!openPopup[0]) {
                    that._togglePin(popup.wrapper, false);
                }

                if (autoHideAfter > 0) {
                    setTimeout(function () {
                        popup.close();
                    }, autoHideAfter);
                }
            },

            _togglePin: function (wrapper, pin) {
                var win = $(window),
                sign = pin ? -1 : 1;

                wrapper.css({
                    top: parseInt(wrapper.css(TOP), 10) + sign * win.scrollTop(),
                    left: parseInt(wrapper.css(LEFT), 10) + sign * win.scrollLeft()
                });
            },

            _attachStaticEvents: function (options, wrapper) {
                var that = this,
                allowHideAfter = options.allowHideAfter,
                attachDelay = !isNaN(allowHideAfter) && allowHideAfter > 0;

                function attachClick(target) {
                    target.on(CLICK + NS, proxy(that._hideStatic, that, wrapper));
                }

                if (options.hideOnClick) {
                    if (attachDelay) {
                        setTimeout(function () {
                            attachClick(wrapper);
                        }, allowHideAfter);
                    } else {
                        attachClick(wrapper);
                    }
                } else if (options.button) {
                    if (attachDelay) {
                        setTimeout(function () {
                            attachClick(wrapper.find(KICLOSE));
                        }, allowHideAfter);
                    } else {
                        attachClick(wrapper.find(KICLOSE));
                    }
                }
            },

            _showStatic: function (wrapper, options) {
                var that = this,
                autoHideAfter = options.autoHideAfter,
                animation = options.animation,
                insertionMethod = options.stacking == UP || options.stacking == LEFT ? "prependTo" : "appendTo",
                attachClick;

                wrapper
                .addClass(that._guid)
                [insertionMethod](options.appendTo)
                .hide()
                .kendoAnimate(animation.open || false);

                that._attachStaticEvents(options, wrapper);

                if (autoHideAfter > 0) {
                    setTimeout(function () {
                        that._hideStatic(wrapper);
                    }, autoHideAfter);
                }
            },

            _hideStatic: function (wrapper) {
                wrapper.kendoAnimate(extend(this.options.animation.close || false, { complete: function () {
                    wrapper.off(NS).find(KICLOSE).off(NS);
                    wrapper.remove();
                } 
                }));
                this._triggerHide(wrapper);
            },

            _triggerHide: function (element) {
                this.trigger(HIDE, { element: element });
                this.angular("cleanup", function () {
                    return { elements: element };
                });
            },

            show: function (content, type) {
                var that = this,
                options = that.options,
                wrapper = $(WRAPPER),
                args, defaultArgs, popup;

                if (!type) {
                    type = INFO;
                }

                if (content !== null && content !== undefined && content !== "") {

                    if (kendo.isFunction(content)) {
                        content = content();
                    }

                    defaultArgs = { typeIcon: type, content: "" };

                    if ($.isPlainObject(content)) {
                        args = extend(defaultArgs, content);
                    } else {
                        args = extend(defaultArgs, { content: content });
                    }

                    wrapper
                    .addClass(KNOTIFICATION + "-" + type)
                    .toggleClass(KNOTIFICATION + "-button", options.button)
                    .attr("data-role", "alert")
                    .css({ width: options.width, height: options.height })
                    .append(that._getCompiled(type)(args));

                    that.angular("compile", function () {
                        return {
                            elements: wrapper,
                            data: [{ dataItem: args}]
                        };
                    });

                    if ($(options.appendTo)[0]) {
                        that._showStatic(wrapper, options);
                    } else {
                        that._showPopup(wrapper, options);
                    }

                    that.trigger(SHOW, { element: wrapper });
                }

                return that;
            },

            info: function (content) {
                return this.show(content, INFO);
            },

            success: function (content) {
                return this.show(content, SUCCESS);
            },

            warning: function (content) {
                return this.show(content, WARNING);
            },

            error: function (content) {
                return this.show(content, ERROR);
            },

            hide: function () {
                var that = this,
                openedNotifications = that.getNotifications();

                if (that.options.appendTo) {
                    openedNotifications.each(function (idx, element) {
                        that._hideStatic($(element));
                    });
                } else {
                    openedNotifications.each(function (idx, element) {
                        var popup = $(element).data("kendoPopup");
                        if (popup) {
                            popup.close();
                        }
                    });
                }

                return that;
            },

            getNotifications: function () {
                var that = this,
                guidElements = $("." + that._guid);

                if (that.options.appendTo) {
                    return guidElements;
                } else {
                    return guidElements.children("." + KNOTIFICATION);
                }
            },

            setOptions: function (newOptions) {
                var that = this,
                options;

                Widget.fn.setOptions.call(that, newOptions);

                options = that.options;

                if (newOptions.templates !== undefined) {
                    that._compileTemplates(options.templates);
                }

                if (newOptions.stacking !== undefined || newOptions.position !== undefined) {
                    that._compileStacking(options.stacking, options.position.top);
                }
            },

            destroy: function () {
                Widget.fn.destroy.call(this);
                this.getNotifications().off(NS).find(KICLOSE).off(NS);
            }
        });

        kendo.ui.plugin(Notification);

    })(window.kendo.jQuery);




    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        Popup = kendo.ui.Popup,
        isFunction = kendo.isFunction,
        isPlainObject = $.isPlainObject,
        extend = $.extend,
        proxy = $.proxy,
        DOCUMENT = $(document),
        isLocalUrl = kendo.isLocalUrl,
        ARIAIDSUFFIX = "_tt_active",
        DESCRIBEDBY = "aria-describedby",
        SHOW = "show",
        HIDE = "hide",
        ERROR = "error",
        CONTENTLOAD = "contentLoad",
        REQUESTSTART = "requestStart",
        KCONTENTFRAME = "k-content-frame",
        TEMPLATE = '<div role="tooltip" class="k-widget k-tooltip#if (!autoHide) {# k-tooltip-closable#}#">#if (!autoHide) {# <div class="k-tooltip-button"><a href="\\#" class="k-icon k-i-close">close</a></div> #}#' +
                '<div class="k-tooltip-content"></div>' +
                '#if (callout){ #<div class="k-callout k-callout-#=dir#"></div>#}#' +
            '</div>',
        IFRAMETEMPLATE = kendo.template(
        "<iframe frameborder='0' class='" + KCONTENTFRAME + "' " +
                "src='#= content.url #'>" +
                    "This page requires frames in order to show content" +
        "</iframe>"),
        NS = ".kendoTooltip",
        POSITIONS = {
            bottom: {
                origin: "bottom center",
                position: "top center"
            },
            top: {
                origin: "top center",
                position: "bottom center"
            },
            left: {
                origin: "center left",
                position: "center right",
                collision: "fit flip"
            },
            right: {
                origin: "center right",
                position: "center left",
                collision: "fit flip"
            },
            center: {
                position: "center center",
                origin: "center center"
            }
        },
        REVERSE = {
            "top": "bottom",
            "bottom": "top",
            "left": "right",
            "right": "left",
            "center": "center"
        },
        DIRCLASSES = {
            bottom: "n",
            top: "s",
            left: "e",
            right: "w",
            center: "n"
        },
        DIMENSIONS = {
            "horizontal": { offset: "top", size: "outerHeight" },
            "vertical": { offset: "left", size: "outerWidth" }
        },
        DEFAULTCONTENT = function (e) {
            return e.target.data(kendo.ns + "title");
        };

        function restoreTitle(element) {
            while (element.length) {
                restoreTitleAttributeForElement(element);
                element = element.parent();
            }
        }

        function restoreTitleAttributeForElement(element) {
            var title = element.data(kendo.ns + "title");
            if (title) {
                element.attr("title", title);
                element.removeData(kendo.ns + "title");
            }
        }

        function saveTitleAttributeForElement(element) {
            var title = element.attr("title");
            if (title) {
                element.data(kendo.ns + "title", title);
                element.attr("title", "");
            }
        }

        function saveTitleAttributes(element) {
            while (element.length && !element.is("body")) {
                saveTitleAttributeForElement(element);
                element = element.parent();
            }
        }

        var Tooltip = Widget.extend({
            init: function (element, options) {
                var that = this,
                axis;

                Widget.fn.init.call(that, element, options);

                axis = that.options.position.match(/left|right/) ? "horizontal" : "vertical";

                that.dimensions = DIMENSIONS[axis];

                that._documentKeyDownHandler = proxy(that._documentKeyDown, that);

                that.element
                .on(that.options.showOn + NS, that.options.filter, proxy(that._showOn, that))
                .on("mouseenter" + NS, that.options.filter, proxy(that._mouseenter, that));

                if (this.options.autoHide) {
                    that.element.on("mouseleave" + NS, that.options.filter, proxy(that._mouseleave, that));
                }
            },

            options: {
                name: "Tooltip",
                filter: "",
                content: DEFAULTCONTENT,
                showAfter: 100,
                callout: true,
                position: "bottom",
                showOn: "mouseenter",
                autoHide: true,
                width: null,
                height: null,
                animation: {
                    open: {
                        effects: "fade:in",
                        duration: 0
                    },
                    close: {
                        effects: "fade:out",
                        duration: 40,
                        hide: true
                    }
                }
            },

            events: [SHOW, HIDE, CONTENTLOAD, ERROR, REQUESTSTART],

            _mouseenter: function (e) {
                saveTitleAttributes($(e.currentTarget));
            },

            _showOn: function (e) {
                var that = this;

                var currentTarget = $(e.currentTarget);
                if (that.options.showOn && that.options.showOn.match(/click|focus/)) {
                    that._show(currentTarget);
                } else {
                    clearTimeout(that.timeout);

                    that.timeout = setTimeout(function () {
                        that._show(currentTarget);
                    }, that.options.showAfter);
                }
            },

            _appendContent: function (target) {
                var that = this,
                contentOptions = that.options.content,
                element = that.content,
                showIframe = that.options.iframe,
                iframe;

                if (isPlainObject(contentOptions) && contentOptions.url) {
                    if (!("iframe" in that.options)) {
                        showIframe = !isLocalUrl(contentOptions.url);
                    }

                    that.trigger(REQUESTSTART, { options: contentOptions, target: target });

                    if (!showIframe) {
                        element.empty();
                        kendo.ui.progress(element, true);

                        // perform AJAX request
                        that._ajaxRequest(contentOptions);
                    } else {
                        element.hide();

                        iframe = element.find("." + KCONTENTFRAME)[0];

                        if (iframe) {
                            // refresh existing iframe
                            iframe.src = contentOptions.url || iframe.src;
                        } else {
                            element.html(IFRAMETEMPLATE({ content: contentOptions }));
                        }

                        element.find("." + KCONTENTFRAME)
                        .off("load" + NS)
                        .on("load" + NS, function () {
                            that.trigger(CONTENTLOAD);
                            element.show();
                        });
                    }
                } else if (contentOptions && isFunction(contentOptions)) {
                    contentOptions = contentOptions({ sender: this, target: target });
                    element.html(contentOptions || "");
                } else {
                    element.html(contentOptions);
                }

                that.angular("compile", function () {
                    return { elements: element };
                });
            },

            _ajaxRequest: function (options) {
                var that = this;

                jQuery.ajax(extend({
                    type: "GET",
                    dataType: "html",
                    cache: false,
                    error: function (xhr, status) {
                        kendo.ui.progress(that.content, false);

                        that.trigger(ERROR, { status: status, xhr: xhr });
                    },
                    success: proxy(function (data) {
                        kendo.ui.progress(that.content, false);

                        that.content.html(data);

                        that.trigger(CONTENTLOAD);
                    }, that)
                }, options));
            },

            _documentKeyDown: function (e) {
                if (e.keyCode === kendo.keys.ESC) {
                    this.hide();
                }
            },

            refresh: function () {
                var that = this,
                popup = that.popup;

                if (popup && popup.options.anchor) {
                    that._appendContent(popup.options.anchor);
                }
            },

            hide: function () {
                if (this.popup) {
                    this.popup.close();
                }
            },

            show: function (target) {
                target = target || this.element;

                saveTitleAttributes(target);
                this._show(target);
            },

            _show: function (target) {
                var that = this,
                current = that.target();

                if (!that.popup) {
                    that._initPopup();
                }

                if (current && current[0] != target[0]) {
                    that.popup.close();
                    that.popup.element.kendoStop(true, true); // animation can be too long to hide the element before it is shown again
                }

                if (!current || current[0] != target[0]) {
                    that._appendContent(target);
                    that.popup.options.anchor = target;
                }

                that.popup.one("deactivate", function () {
                    restoreTitle(target);
                    target.removeAttr(DESCRIBEDBY);

                    this.element
                    .removeAttr("id")
                    .attr("aria-hidden", true);

                    DOCUMENT.off("keydown" + NS, that._documentKeyDownHandler);
                });

                that.popup.open();
            },

            _initPopup: function () {
                var that = this,
                options = that.options,
                wrapper = $(kendo.template(TEMPLATE)({
                    callout: options.callout && options.position !== "center",
                    dir: DIRCLASSES[options.position],
                    autoHide: options.autoHide
                }));

                that.popup = new Popup(wrapper, extend({
                    activate: function () {
                        var anchor = this.options.anchor,
                        ariaId = anchor[0].id || that.element[0].id;

                        if (ariaId) {
                            anchor.attr(DESCRIBEDBY, ariaId + ARIAIDSUFFIX);
                            this.element.attr("id", ariaId + ARIAIDSUFFIX);
                        }

                        if (options.callout) {
                            that._positionCallout();
                        }

                        this.element.removeAttr("aria-hidden");

                        DOCUMENT.on("keydown" + NS, that._documentKeyDownHandler);

                        that.trigger(SHOW);
                    },
                    close: function () {
                        that.trigger(HIDE);
                    },
                    copyAnchorStyles: false,
                    animation: options.animation
                }, POSITIONS[options.position]));

                wrapper.css({
                    width: options.width,
                    height: options.height
                });

                that.content = wrapper.find(".k-tooltip-content");
                that.arrow = wrapper.find(".k-callout");

                if (options.autoHide) {
                    wrapper.on("mouseleave" + NS, proxy(that._mouseleave, that));
                } else {
                    wrapper.on("click" + NS, ".k-tooltip-button", proxy(that._closeButtonClick, that));
                }
            },

            _closeButtonClick: function (e) {
                e.preventDefault();
                this.hide();
            },

            _mouseleave: function (e) {
                if (this.popup) {
                    var element = $(e.currentTarget),
                    offset = element.offset(),
                    pageX = e.pageX,
                    pageY = e.pageY;

                    offset.right = offset.left + element.outerWidth();
                    offset.bottom = offset.top + element.outerHeight();

                    if (pageX > offset.left && pageX < offset.right && pageY > offset.top && pageY < offset.bottom) {
                        return;
                    }

                    this.popup.close();
                } else {
                    restoreTitle($(e.currentTarget));
                }
                clearTimeout(this.timeout);
            },

            _positionCallout: function () {
                var that = this,
                position = that.options.position,
                dimensions = that.dimensions,
                offset = dimensions.offset,
                popup = that.popup,
                anchor = popup.options.anchor,
                anchorOffset = $(anchor).offset(),
                arrowBorder = parseInt(that.arrow.css("border-top-width"), 10),
                elementOffset = $(popup.element).offset(),
                cssClass = DIRCLASSES[popup.flipped ? REVERSE[position] : position],
                offsetAmount = anchorOffset[offset] - elementOffset[offset] + ($(anchor)[dimensions.size]() / 2) - arrowBorder;

                that.arrow
               .removeClass("k-callout-n k-callout-s k-callout-w k-callout-e")
               .addClass("k-callout-" + cssClass)
               .css(offset, offsetAmount);
            },

            target: function () {
                if (this.popup) {
                    return this.popup.options.anchor;
                }
                return null;
            },

            destroy: function () {
                var popup = this.popup;

                if (popup) {
                    popup.element.off(NS);
                    popup.destroy();
                }

                this.element.off(NS);

                DOCUMENT.off("keydown" + NS, this._documentKeyDownHandler);

                Widget.fn.destroy.call(this);
            }
        });

        kendo.ui.plugin(Tooltip);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        keys = kendo.keys,
        support = kendo.support,
        htmlEncode = kendo.htmlEncode,
        activeElement = kendo._activeElement,
        ID = "id",
        LI = "li",
        CHANGE = "change",
        CHARACTER = "character",
        FOCUSED = "k-state-focused",
        HOVER = "k-state-hover",
        LOADING = "k-loading",
        OPEN = "open",
        CLOSE = "close",
        SELECT = "select",
        SELECTED = "selected",
        PROGRESS = "progress",
        REQUESTEND = "requestEnd",
        WIDTH = "width",
        extend = $.extend,
        proxy = $.proxy,
        browser = support.browser,
        isIE8 = browser.msie && browser.version < 9,
        quotRegExp = /"/g,
        alternativeNames = {
            "ComboBox": "DropDownList",
            "DropDownList": "ComboBox"
        };

        var List = kendo.ui.DataBoundWidget.extend({
            init: function (element, options) {
                var that = this,
                ns = that.ns,
                id;

                Widget.fn.init.call(that, element, options);
                element = that.element;

                that._isSelect = element.is(SELECT);
                that._template();

                that.ul = $('<ul unselectable="on" class="k-list k-reset"/>')
                        .css({ overflow: support.kineticScrollNeeded ? "" : "auto" })
                        .on("mouseenter" + ns, LI, function () { $(this).addClass(HOVER); })
                        .on("mouseleave" + ns, LI, function () { $(this).removeClass(HOVER); })
                        .on("click" + ns, LI, proxy(that._click, that))
                        .attr({
                            tabIndex: -1,
                            role: "listbox",
                            "aria-hidden": true
                        });

                that.list = $("<div class='k-list-container'/>")
                        .append(that.ul)
                        .on("mousedown" + ns, proxy(that._listMousedown, that));

                id = element.attr(ID);

                if (id) {
                    that.list.attr(ID, id + "-list");
                    that.ul.attr(ID, id + "_listbox");
                    that._optionID = id + "_option_selected";
                }

                that._header();
                that._accessors();
                that._initValue();
            },

            options: {
                valuePrimitive: false,
                headerTemplate: ""
            },

            setOptions: function (options) {
                Widget.fn.setOptions.call(this, options);

                if (options && options.enable !== undefined) {
                    options.enabled = options.enable;
                }
            },

            focus: function () {
                this._focused.focus();
            },

            readonly: function (readonly) {
                this._editable({
                    readonly: readonly === undefined ? true : readonly,
                    disable: false
                });
            },

            enable: function (enable) {
                this._editable({
                    readonly: false,
                    disable: !(enable = enable === undefined ? true : enable)
                });
            },

            _listMousedown: function (e) {
                if (!this.filterInput || this.filterInput[0] !== e.target) {
                    e.preventDefault();
                }
            },

            _filterSource: function (filter, force) {
                var that = this;
                var options = that.options;
                var dataSource = that.dataSource;
                var expression = extend({}, dataSource.filter() || {});

                var removed = removeFiltersForField(expression, options.dataTextField);

                if ((filter || removed) && that.trigger("filtering", { filter: filter })) {
                    return;
                }

                if (filter) {
                    expression = expression.filters || [];
                    expression.push(filter);
                }

                if (!force) {
                    dataSource.filter(expression);
                } else {
                    dataSource.read(expression);
                }
            },

            _header: function () {
                var that = this;
                var template = that.options.headerTemplate;
                var header;

                if ($.isFunction(template)) {
                    template = template({});
                }

                if (template) {
                    that.list.prepend(template);

                    header = that.ul.prev();

                    that.header = header[0] ? header : null;
                    if (that.header) {
                        that.angular("compile", function () {
                            return { elements: that.header };
                        });
                    }
                }
            },

            _initValue: function () {
                var that = this,
                value = that.options.value;

                if (value !== null) {
                    that.element.val(value);
                } else {
                    value = that._accessor();
                    that.options.value = value;
                }

                that._old = value;
            },

            _ignoreCase: function () {
                var that = this,
                model = that.dataSource.reader.model,
                field;

                if (model && model.fields) {
                    field = model.fields[that.options.dataTextField];

                    if (field && field.type && field.type !== "string") {
                        that.options.ignoreCase = false;
                    }
                }
            },

            items: function () {
                return this.ul[0].children;
            },

            current: function (candidate) {
                var that = this;
                var focused = that._focused.add(that.filterInput);
                var id = that._optionID;

                if (candidate !== undefined) {
                    if (that._current) {
                        that._current
                        .removeClass(FOCUSED)
                        .removeAttr("aria-selected")
                        .removeAttr(ID);

                        focused.removeAttr("aria-activedescendant");
                    }

                    if (candidate) {
                        candidate.addClass(FOCUSED);
                        that._scroll(candidate);

                        if (id) {
                            candidate.attr("id", id);
                            focused.attr("aria-activedescendant", id);
                        }
                    }

                    that._current = candidate;
                } else {
                    return that._current;
                }
            },

            destroy: function () {
                var that = this,
                ns = that.ns;

                Widget.fn.destroy.call(that);

                that._unbindDataSource();

                that.ul.off(ns);
                that.list.off(ns);

                if (that._touchScroller) {
                    that._touchScroller.destroy();
                }

                that.popup.destroy();

                if (that._form) {
                    that._form.off("reset", that._resetHandler);
                }
            },

            dataItem: function (index) {
                var that = this;


                if (index === undefined) {
                    index = that.selectedIndex;
                } else if (typeof index !== "number") {
                    index = $(that.items()).index(index);
                }

                return that._data()[index];
            },

            _accessors: function () {
                var that = this;
                var element = that.element;
                var options = that.options;
                var getter = kendo.getter;
                var textField = element.attr(kendo.attr("text-field"));
                var valueField = element.attr(kendo.attr("value-field"));

                if (!options.dataTextField && textField) {
                    options.dataTextField = textField;
                }

                if (!options.dataValueField && valueField) {
                    options.dataValueField = valueField;
                }

                that._text = getter(options.dataTextField);
                that._value = getter(options.dataValueField);
            },

            _aria: function (id) {
                var that = this,
                options = that.options,
                element = that._focused.add(that.filterInput);

                if (options.suggest !== undefined) {
                    element.attr("aria-autocomplete", options.suggest ? "both" : "list");
                }

                id = id ? id + " " + that.ul[0].id : that.ul[0].id;

                element.attr("aria-owns", id);

                that.ul.attr("aria-live", !options.filter || options.filter === "none" ? "off" : "polite");
            },

            _blur: function () {
                var that = this;

                that._change();
                that.close();
            },

            _change: function () {
                var that = this,
                index = that.selectedIndex,
                optionValue = that.options.value,
                value = that.value(),
                trigger;

                if (that._isSelect && !that._bound && optionValue) {
                    value = optionValue;
                }

                if (value !== that._old) {
                    trigger = true;
                } else if (index !== undefined && index !== that._oldIndex) {
                    trigger = true;
                }

                if (trigger) {
                    that._old = value;
                    that._oldIndex = index;

                    // trigger the DOM change event so any subscriber gets notified
                    that.element.trigger(CHANGE);

                    that.trigger(CHANGE);
                }
            },

            _click: function (e) {
                if (!e.isDefaultPrevented()) {
                    this._accept($(e.currentTarget));
                }
            },

            _data: function () {
                return this.dataSource.view();
            },

            _enable: function () {
                var that = this,
                options = that.options,
                disabled = that.element.is("[disabled]");

                if (options.enable !== undefined) {
                    options.enabled = options.enable;
                }

                if (!options.enabled || disabled) {
                    that.enable(false);
                } else {
                    that.readonly(that.element.is("[readonly]"));
                }
            },

            _focus: function (li) {
                var that = this;
                var userTriggered = true;

                if (that.popup.visible() && li && that.trigger(SELECT, { item: li })) {
                    that.close();
                    return;
                }

                that._select(li);
                that._triggerCascade(userTriggered);

                that._blur();
            },

            _index: function (value) {
                var that = this,
                idx,
                length,
                data = that._data();

                for (idx = 0, length = data.length; idx < length; idx++) {
                    if (that._dataValue(data[idx]) == value) {
                        return idx;
                    }
                }

                return -1;
            },

            _dataValue: function (dataItem) {
                var value = this._value(dataItem);

                if (value === undefined) {
                    value = this._text(dataItem);
                }

                return value;
            },

            _height: function (length) {
                if (length) {
                    var that = this,
                    list = that.list,
                    height = that.options.height,
                    visible = that.popup.visible(),
                    offsetTop,
                    popups;

                    popups = list.add(list.parent(".k-animation-container")).show();

                    height = that.ul[0].scrollHeight > height ? height : "auto";

                    popups.height(height);

                    if (height !== "auto") {
                        offsetTop = that.ul[0].offsetTop;

                        if (offsetTop) {
                            height = list.height() - offsetTop;
                        }
                    }

                    that.ul.height(height);

                    if (!visible) {
                        popups.hide();
                    }
                }
            },

            _adjustListWidth: function () {
                var list = this.list,
                width = list[0].style.width,
                wrapper = this.wrapper,
                computedStyle, computedWidth;

                if (!list.data(WIDTH) && width) {
                    return;
                }

                computedStyle = window.getComputedStyle ? window.getComputedStyle(wrapper[0], null) : 0;
                computedWidth = computedStyle ? parseFloat(computedStyle.width) : wrapper.outerWidth();

                if (computedStyle && browser.msie) { // getComputedStyle returns different box in IE.
                    computedWidth += parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight) + parseFloat(computedStyle.borderLeftWidth) + parseFloat(computedStyle.borderRightWidth);
                }

                if (list.css("box-sizing") !== "border-box") {
                    width = computedWidth - (list.outerWidth() - list.width());
                } else {
                    width = computedWidth;
                }

                list.css({
                    fontFamily: wrapper.css("font-family"),
                    width: width
                })
            .data(WIDTH, width);

                return true;
            },

            _openHandler: function (e) {
                this._adjustListWidth();

                if (this.trigger(OPEN)) {
                    e.preventDefault();
                } else {
                    this._focused.attr("aria-expanded", true);
                    this.ul.attr("aria-hidden", false);
                }
            },

            _closeHandler: function (e) {
                if (this.trigger(CLOSE)) {
                    e.preventDefault();
                } else {
                    this._focused.attr("aria-expanded", false);
                    this.ul.attr("aria-hidden", true);
                }
            },

            _firstOpen: function () {
                this._height(this._data().length);
            },

            _popup: function () {
                var that = this;

                that.popup = new ui.Popup(that.list, extend({}, that.options.popup, {
                    anchor: that.wrapper,
                    open: proxy(that._openHandler, that),
                    close: proxy(that._closeHandler, that),
                    animation: that.options.animation,
                    isRtl: support.isRtl(that.wrapper)
                }));

                that.popup.one(OPEN, proxy(that._firstOpen, that));
                that._touchScroller = kendo.touchScroller(that.popup.element);
            },

            _makeUnselectable: function () {
                if (isIE8) {
                    this.list.find("*").not(".k-textbox").attr("unselectable", "on");
                }
            },

            _toggleHover: function (e) {
                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
            },

            _toggle: function (open, preventFocus) {
                var that = this;
                var touchEnabled = support.touch && support.MSPointers && support.pointers;

                open = open !== undefined ? open : !that.popup.visible();

                if (!preventFocus && !touchEnabled && that._focused[0] !== activeElement()) {
                    that._focused.focus();
                }

                that[open ? OPEN : CLOSE]();
            },

            _scroll: function (item) {

                if (!item) {
                    return;
                }

                if (item[0]) {
                    item = item[0];
                }

                var ul = this.ul[0],
                itemOffsetTop = item.offsetTop,
                itemOffsetHeight = item.offsetHeight,
                ulScrollTop = ul.scrollTop,
                ulOffsetHeight = ul.clientHeight,
                bottomDistance = itemOffsetTop + itemOffsetHeight,
                touchScroller = this._touchScroller,
                yDimension, offsetHeight;

                if (touchScroller) {
                    yDimension = touchScroller.dimensions.y;

                    if (yDimension.enabled && itemOffsetTop > yDimension.size) {
                        itemOffsetTop = itemOffsetTop - yDimension.size + itemOffsetHeight + 4;

                        touchScroller.scrollTo(0, -itemOffsetTop);
                    }
                } else {
                    offsetHeight = this.header ? this.header.outerHeight() : 0;
                    offsetHeight += this.filterInput ? this.filterInput.outerHeight() : 0;

                    ul.scrollTop = ulScrollTop > itemOffsetTop ?
                               (itemOffsetTop - offsetHeight) : bottomDistance > (ulScrollTop + ulOffsetHeight) ?
                               (bottomDistance - ulOffsetHeight - offsetHeight) : ulScrollTop;
                }
            },

            _template: function () {
                var that = this,
                options = that.options,
                template = options.template,
                hasDataSource = options.dataSource;

                if (that._isSelect && that.element[0].length) {
                    if (!hasDataSource) {
                        options.dataTextField = options.dataTextField || "text";
                        options.dataValueField = options.dataValueField || "value";
                    }
                }

                if (!template) {
                    that.template = kendo.template('<li tabindex="-1" role="option" unselectable="on" class="k-item">${' + kendo.expr(options.dataTextField, "data") + "}</li>", { useWithBlock: false });
                } else {
                    template = kendo.template(template);
                    that.template = function (data) {
                        return '<li tabindex="-1" role="option" unselectable="on" class="k-item">' + template(data) + "</li>";
                    };
                }
            },

            _triggerCascade: function (userTriggered) {
                var that = this,
                value = that.value();

                if ((!that._bound && value) || that._old !== value) {
                    that.trigger("cascade", { userTriggered: userTriggered });
                }
            },

            _unbindDataSource: function () {
                var that = this;

                that.dataSource.unbind(CHANGE, that._refreshHandler)
                           .unbind(PROGRESS, that._progressHandler)
                           .unbind(REQUESTEND, that._requestEndHandler)
                           .unbind("error", that._errorHandler);
            }
        });

        extend(List, {
            inArray: function (node, parentNode) {
                var idx, length, siblings = parentNode.children;

                if (!node || node.parentNode !== parentNode) {
                    return -1;
                }

                for (idx = 0, length = siblings.length; idx < length; idx++) {
                    if (node === siblings[idx]) {
                        return idx;
                    }
                }

                return -1;
            }
        });

        kendo.ui.List = List;

        ui.Select = List.extend({
            init: function (element, options) {
                List.fn.init.call(this, element, options);
                this._initial = this.element.val();
            },

            setDataSource: function (dataSource) {
                this.options.dataSource = dataSource;

                this._dataSource();
                this._bound = false;

                if (this.options.autoBind) {
                    this.dataSource.fetch();
                }
            },

            close: function () {
                this.popup.close();
            },

            select: function (li) {
                var that = this;

                if (li === undefined) {
                    return that.selectedIndex;
                } else {
                    that._select(li);
                    that._triggerCascade();
                    that._old = that._accessor();
                    that._oldIndex = that.selectedIndex;
                }
            },

            search: function (word) {
                word = typeof word === "string" ? word : this.text();
                var that = this;
                var length = word.length;
                var options = that.options;
                var ignoreCase = options.ignoreCase;
                var filter = options.filter;
                var field = options.dataTextField;

                clearTimeout(that._typing);

                if (!length || length >= options.minLength) {
                    that._state = "filter";
                    if (filter === "none") {
                        that._filter(word);
                    } else {
                        that._open = true;
                        that._filterSource({
                            value: ignoreCase ? word.toLowerCase() : word,
                            field: field,
                            operator: filter,
                            ignoreCase: ignoreCase
                        });
                    }
                }
            },

            _accessor: function (value, idx) {
                var element = this.element[0],
                isSelect = this._isSelect,
                selectedIndex = element.selectedIndex,
                option;

                if (value === undefined) {
                    if (isSelect) {
                        if (selectedIndex > -1) {
                            option = element.options[selectedIndex];

                            if (option) {
                                value = option.value;
                            }
                        }
                    } else {
                        value = element.value;
                    }
                    return value;
                } else {
                    if (isSelect) {
                        if (selectedIndex > -1) {
                            element.options[selectedIndex].removeAttribute(SELECTED);
                        }

                        element.selectedIndex = idx;
                        option = element.options[idx];
                        if (option) {
                            option.setAttribute(SELECTED, SELECTED);
                        }
                    } else {
                        element.value = value;
                    }
                }
            },

            _hideBusy: function () {
                var that = this;
                clearTimeout(that._busy);
                that._arrow.removeClass(LOADING);
                that._focused.attr("aria-busy", false);
                that._busy = null;
            },

            _showBusy: function () {
                var that = this;

                that._request = true;

                if (that._busy) {
                    return;
                }

                that._busy = setTimeout(function () {
                    if (that._arrow) { //destroyed after request start
                        that._focused.attr("aria-busy", true);
                        that._arrow.addClass(LOADING);
                    }
                }, 100);
            },

            _requestEnd: function () {
                this._request = false;
            },

            _dataSource: function () {
                var that = this,
                element = that.element,
                options = that.options,
                dataSource = options.dataSource || {},
                idx;

                dataSource = $.isArray(dataSource) ? { data: dataSource} : dataSource;

                if (that._isSelect) {
                    idx = element[0].selectedIndex;
                    if (idx > -1) {
                        options.index = idx;
                    }

                    dataSource.select = element;
                    dataSource.fields = [{ field: options.dataTextField },
                                     { field: options.dataValueField}];
                }

                if (that.dataSource && that._refreshHandler) {
                    that._unbindDataSource();
                } else {
                    that._refreshHandler = proxy(that.refresh, that);
                    that._progressHandler = proxy(that._showBusy, that);
                    that._requestEndHandler = proxy(that._requestEnd, that);
                    that._errorHandler = proxy(that._hideBusy, that);
                }

                that.dataSource = kendo.data.DataSource.create(dataSource)
                                   .bind(CHANGE, that._refreshHandler)
                                   .bind(PROGRESS, that._progressHandler)
                                   .bind(REQUESTEND, that._requestEndHandler)
                                   .bind("error", that._errorHandler);
            },

            _get: function (li) {
                var that = this,
                data = that._data(),
                idx, length;

                if (typeof li === "function") {
                    for (idx = 0, length = data.length; idx < length; idx++) {
                        if (li(data[idx])) {
                            li = idx;
                            break;
                        }
                    }
                }

                if (typeof li === "number") {
                    if (li < 0) {
                        return $();
                    }

                    li = $(that.ul[0].children[li]);
                }

                if (li && li.nodeType) {
                    li = $(li);
                }

                return li;
            },

            _move: function (e) {
                var that = this,
                key = e.keyCode,
                ul = that.ul[0],
                methodName = that.popup.visible() ? "_select" : "_accept",
                current = that._current,
                down = key === keys.DOWN,
                firstChild,
                pressed;

                if (key === keys.UP || down) {
                    if (e.altKey) {
                        that.toggle(down);
                    } else {
                        firstChild = ul.firstChild;
                        if (!firstChild && !that._accessor() && that._state !== "filter") {
                            if (!that._fetch) {
                                that.dataSource.one(CHANGE, function () {
                                    that._move(e);
                                    that._fetch = false;
                                });

                                that._fetch = true;
                                that._filterSource();
                            }

                            e.preventDefault();

                            return true; //pressed
                        }

                        if (down) {
                            if (!current || (that.selectedIndex === -1 && !that.value() && current[0] === firstChild)) {
                                current = firstChild;
                            } else {
                                current = current[0].nextSibling;
                                if (!current && firstChild === ul.lastChild) {
                                    current = firstChild;
                                }
                            }

                            that[methodName](current);
                        } else {
                            current = current ? current[0].previousSibling : ul.lastChild;
                            if (!current && firstChild === ul.lastChild) {
                                current = firstChild;
                            }

                            that[methodName](current);
                        }
                    }

                    e.preventDefault();
                    pressed = true;
                } else if (key === keys.ENTER || key === keys.TAB) {
                    if (that.popup.visible()) {
                        e.preventDefault();
                    }

                    if (!that.popup.visible() && (!current || !current.hasClass("k-state-selected"))) {
                        current = null;
                    }

                    that._accept(current, key);
                    pressed = true;
                } else if (key === keys.ESC) {
                    if (that.popup.visible()) {
                        e.preventDefault();
                    }
                    that.close();
                    pressed = true;
                }

                return pressed;
            },

            _selectItem: function () {
                var that = this,
                notBound = that._bound === undefined,
                options = that.options,
                useOptionIndex,
                value;

                useOptionIndex = that._isSelect && !that._initial && !options.value && options.index && !that._bound;

                if (!useOptionIndex) {
                    value = that._selectedValue || (notBound && options.value) || that._accessor();
                }

                if (value) {
                    that.value(value);
                } else if (notBound) {
                    that.select(options.index);
                }
            },

            _fetchItems: function (value) {
                var that = this,
                hasItems = that.ul[0].firstChild;

                //if request is started avoid datasource.fetch
                if (that._request) {
                    return true;
                }

                if (!that._bound && !that._fetch && !hasItems) {
                    if (that.options.cascadeFrom) {
                        return !hasItems;
                    }

                    that.dataSource.one(CHANGE, function () {
                        that._old = undefined;
                        that.value(value);
                        that._fetch = false;
                    });

                    that._fetch = true;
                    that.dataSource.fetch();

                    return true;
                }
            },

            _options: function (data, optionLabel) {
                var that = this,
                element = that.element,
                length = data.length,
                options = "",
                option,
                dataItem,
                dataText,
                dataValue,
                idx = 0;

                if (optionLabel) {
                    idx = 1;
                    options = optionLabel;
                }

                for (; idx < length; idx++) {
                    option = "<option";
                    dataItem = data[idx];
                    dataText = that._text(dataItem);
                    dataValue = that._value(dataItem);

                    if (dataValue !== undefined) {
                        dataValue += "";

                        if (dataValue.indexOf('"') !== -1) {
                            dataValue = dataValue.replace(quotRegExp, "&quot;");
                        }

                        option += ' value="' + dataValue + '"';
                    }

                    option += ">";

                    if (dataText !== undefined) {
                        option += htmlEncode(dataText);
                    }

                    option += "</option>";
                    options += option;
                }

                element.html(options);
            },

            _reset: function () {
                var that = this,
                element = that.element,
                formId = element.attr("form"),
                form = formId ? $("#" + formId) : element.closest("form");

                if (form[0]) {
                    that._resetHandler = function () {
                        setTimeout(function () {
                            that.value(that._initial);
                        });
                    };

                    that._form = form.on("reset", that._resetHandler);
                }
            },

            _cascade: function () {
                var that = this,
                options = that.options,
                cascade = options.cascadeFrom,
                parent, parentElement,
                select, valueField,
                change;

                if (cascade) {
                    that._selectedValue = options.value || that._accessor();

                    parentElement = $("#" + cascade);
                    parent = parentElement.data("kendo" + options.name);

                    if (!parent) {
                        parent = parentElement.data("kendo" + alternativeNames[options.name]);
                    }

                    if (!parent) {
                        return;
                    }

                    options.autoBind = false;
                    valueField = options.cascadeFromField || parent.options.dataValueField;

                    change = function () {
                        that.dataSource.unbind(CHANGE, change);

                        var value = that._selectedValue || that.value();
                        if (that._userTriggered) {
                            that._clearSelection(parent, true);
                        } else if (value) {
                            that.value(value);
                            if (!that.dataSource.view()[0] || that.selectedIndex === -1) {
                                that._clearSelection(parent, true);
                            }
                        } else {
                            that.select(options.index);
                        }

                        that.enable();
                        that._triggerCascade(that._userTriggered);
                        that._userTriggered = false;
                    };
                    select = function () {
                        var dataItem = parent.dataItem(),
                        filterValue = dataItem ? parent._value(dataItem) : null,
                        expressions, filters;

                        if (filterValue || filterValue === 0) {
                            expressions = that.dataSource.filter() || {};
                            removeFiltersForField(expressions, valueField);
                            filters = expressions.filters || [];

                            filters.push({
                                field: valueField,
                                operator: "eq",
                                value: filterValue
                            });

                            var handler = function () {
                                that.unbind("dataBound", handler);
                                change.apply(that, arguments);
                            };

                            that.first("dataBound", handler);

                            that.dataSource.filter(filters);

                        } else {
                            that.enable(false);
                            that._clearSelection(parent);
                            that._triggerCascade(that._userTriggered);
                            that._userTriggered = false;
                        }
                    };

                    parent.first("cascade", function (e) {
                        that._userTriggered = e.userTriggered;
                        select();
                    });

                    //refresh was called
                    if (parent._bound) {
                        select();
                    } else if (!parent.value()) {
                        that.enable(false);
                    }
                }
            }
        });

        function removeFiltersForField(expression, field) {
            var filters;
            var found = false;

            if (expression.filters) {
                filters = $.grep(expression.filters, function (filter) {
                    found = removeFiltersForField(filter, field);
                    if (filter.filters) {
                        return filter.filters.length;
                    } else {
                        return filter.field != field;
                    }
                });

                if (!found && expression.filters.length !== filters.length) {
                    found = true;
                }

                expression.filters = filters;
            }

            return found;
        }
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        support = kendo.support,
        ui = kendo.ui,
        Widget = ui.Widget,
        keys = kendo.keys,
        parse = kendo.parseDate,
        adjustDST = kendo.date.adjustDST,
        extractFormat = kendo._extractFormat,
        template = kendo.template,
        getCulture = kendo.getCulture,
        transitions = kendo.support.transitions,
        transitionOrigin = transitions ? transitions.css + "transform-origin" : "",
        cellTemplate = template('<td#=data.cssClass# role="gridcell"><a tabindex="-1" class="k-link" href="\\#" data-#=data.ns#value="#=data.dateString#">#=data.value#</a></td>', { useWithBlock: false }),
        emptyCellTemplate = template('<td role="gridcell">&nbsp;</td>', { useWithBlock: false }),
        browser = kendo.support.browser,
        isIE8 = browser.msie && browser.version < 9,
        ns = ".kendoCalendar",
        CLICK = "click" + ns,
        KEYDOWN_NS = "keydown" + ns,
        ID = "id",
        MIN = "min",
        LEFT = "left",
        SLIDE = "slideIn",
        MONTH = "month",
        CENTURY = "century",
        CHANGE = "change",
        NAVIGATE = "navigate",
        VALUE = "value",
        HOVER = "k-state-hover",
        DISABLED = "k-state-disabled",
        FOCUSED = "k-state-focused",
        OTHERMONTH = "k-other-month",
        OTHERMONTHCLASS = ' class="' + OTHERMONTH + '"',
        TODAY = "k-nav-today",
        CELLSELECTOR = "td:has(.k-link)",
        BLUR = "blur" + ns,
        FOCUS = "focus",
        FOCUS_WITH_NS = FOCUS + ns,
        MOUSEENTER = support.touch ? "touchstart" : "mouseenter",
        MOUSEENTER_WITH_NS = support.touch ? "touchstart" + ns : "mouseenter" + ns,
        MOUSELEAVE = support.touch ? "touchend" + ns + " touchmove" + ns : "mouseleave" + ns,
        MS_PER_MINUTE = 60000,
        MS_PER_DAY = 86400000,
        PREVARROW = "_prevArrow",
        NEXTARROW = "_nextArrow",
        ARIA_DISABLED = "aria-disabled",
        ARIA_SELECTED = "aria-selected",
        proxy = $.proxy,
        extend = $.extend,
        DATE = Date,
        views = {
            month: 0,
            year: 1,
            decade: 2,
            century: 3
        };

        var Calendar = Widget.extend({
            init: function (element, options) {
                var that = this, value, id;

                Widget.fn.init.call(that, element, options);

                element = that.wrapper = that.element;
                options = that.options;

                options.url = window.unescape(options.url);

                that._templates();

                that._header();

                that._footer(that.footer);

                id = element
                    .addClass("k-widget k-calendar")
                    .on(MOUSEENTER_WITH_NS + " " + MOUSELEAVE, CELLSELECTOR, mousetoggle)
                    .on(KEYDOWN_NS, "table.k-content", proxy(that._move, that))
                    .on(CLICK, CELLSELECTOR, function (e) {
                        var link = e.currentTarget.firstChild;

                        if (link.href.indexOf("#") != -1) {
                            e.preventDefault();
                        }

                        that._click($(link));
                    })
                    .on("mouseup" + ns, "table.k-content, .k-footer", function () {
                        that._focusView(that.options.focusOnNav !== false);
                    })
                    .attr(ID);

                if (id) {
                    that._cellID = id + "_cell_selected";
                }

                normalize(options);
                value = parse(options.value, options.format, options.culture);

                that._index = views[options.start];
                that._current = new DATE(+restrictValue(value, options.min, options.max));

                that._addClassProxy = function () {
                    that._active = true;
                    that._cell.addClass(FOCUSED);
                };

                that._removeClassProxy = function () {
                    that._active = false;
                    that._cell.removeClass(FOCUSED);
                };

                that.value(value);

                kendo.notify(that);
            },

            options: {
                name: "Calendar",
                value: null,
                min: new DATE(1900, 0, 1),
                max: new DATE(2099, 11, 31),
                dates: [],
                url: "",
                culture: "",
                footer: "",
                format: "",
                month: {},
                start: MONTH,
                depth: MONTH,
                animation: {
                    horizontal: {
                        effects: SLIDE,
                        reverse: true,
                        duration: 500,
                        divisor: 2
                    },
                    vertical: {
                        effects: "zoomIn",
                        duration: 400
                    }
                }
            },

            events: [
            CHANGE,
            NAVIGATE
        ],

            setOptions: function (options) {
                var that = this;

                normalize(options);

                if (!options.dates[0]) {
                    options.dates = that.options.dates;
                }

                Widget.fn.setOptions.call(that, options);

                that._templates();

                that._footer(that.footer);
                that._index = views[that.options.start];

                that.navigate();
            },

            destroy: function () {
                var that = this,
                today = that._today;

                that.element.off(ns);
                that._title.off(ns);
                that[PREVARROW].off(ns);
                that[NEXTARROW].off(ns);

                kendo.destroy(that._table);

                if (today) {
                    kendo.destroy(today.off(ns));
                }

                Widget.fn.destroy.call(that);
            },

            current: function () {
                return this._current;
            },

            view: function () {
                return this._view;
            },

            focus: function (table) {
                table = table || this._table;
                this._bindTable(table);
                table.focus();
            },

            min: function (value) {
                return this._option(MIN, value);
            },

            max: function (value) {
                return this._option("max", value);
            },

            navigateToPast: function () {
                this._navigate(PREVARROW, -1);
            },

            navigateToFuture: function () {
                this._navigate(NEXTARROW, 1);
            },

            navigateUp: function () {
                var that = this,
                index = that._index;

                if (that._title.hasClass(DISABLED)) {
                    return;
                }

                that.navigate(that._current, ++index);
            },

            navigateDown: function (value) {
                var that = this,
            index = that._index,
            depth = that.options.depth;

                if (!value) {
                    return;
                }

                if (index === views[depth]) {
                    if (+that._value != +value) {
                        that.value(value);
                        that.trigger(CHANGE);
                    }
                    return;
                }

                that.navigate(value, --index);
            },

            navigate: function (value, view) {
                view = isNaN(view) ? views[view] : view;

                var that = this,
                options = that.options,
                culture = options.culture,
                min = options.min,
                max = options.max,
                title = that._title,
                from = that._table,
                old = that._oldTable,
                selectedValue = that._value,
                currentValue = that._current,
                future = value && +value > +currentValue,
                vertical = view !== undefined && view !== that._index,
                to, currentView, compare,
                disabled;

                if (!value) {
                    value = currentValue;
                }

                that._current = value = new DATE(+restrictValue(value, min, max));

                if (view === undefined) {
                    view = that._index;
                } else {
                    that._index = view;
                }

                that._view = currentView = calendar.views[view];
                compare = currentView.compare;

                disabled = view === views[CENTURY];
                title.toggleClass(DISABLED, disabled).attr(ARIA_DISABLED, disabled);

                disabled = compare(value, min) < 1;
                that[PREVARROW].toggleClass(DISABLED, disabled).attr(ARIA_DISABLED, disabled);

                disabled = compare(value, max) > -1;
                that[NEXTARROW].toggleClass(DISABLED, disabled).attr(ARIA_DISABLED, disabled);

                if (from && old && old.data("animating")) {
                    old.kendoStop(true, true);
                    from.kendoStop(true, true);
                }

                that._oldTable = from;

                if (!from || that._changeView) {
                    title.html(currentView.title(value, min, max, culture));

                    that._table = to = $(currentView.content(extend({
                        min: min,
                        max: max,
                        date: value,
                        url: options.url,
                        dates: options.dates,
                        format: options.format,
                        culture: culture
                    }, that[currentView.name])));

                    makeUnselectable(to);

                    that._animate({
                        from: from,
                        to: to,
                        vertical: vertical,
                        future: future
                    });

                    that._focus(value);
                    that.trigger(NAVIGATE);
                }

                if (view === views[options.depth] && selectedValue) {
                    that._class("k-state-selected", currentView.toDateString(selectedValue));
                }

                that._class(FOCUSED, currentView.toDateString(value));

                if (!from && that._cell) {
                    that._cell.removeClass(FOCUSED);
                }

                that._changeView = true;
            },

            value: function (value) {
                var that = this,
            view = that._view,
            options = that.options,
            old = that._view,
            min = options.min,
            max = options.max;

                if (value === undefined) {
                    return that._value;
                }

                value = parse(value, options.format, options.culture);

                if (value !== null) {
                    value = new DATE(+value);

                    if (!isInRange(value, min, max)) {
                        value = null;
                    }
                }

                that._value = value;

                if (old && value === null && that._cell) {
                    that._cell.removeClass("k-state-selected");
                } else {
                    that._changeView = !value || view && view.compare(value, that._current) !== 0;
                    that.navigate(value);
                }
            },

            _move: function (e) {
                var that = this,
                options = that.options,
                key = e.keyCode,
                view = that._view,
                index = that._index,
                currentValue = new DATE(+that._current),
                isRtl = kendo.support.isRtl(that.wrapper),
                value, prevent, method, temp;

                if (e.target === that._table[0]) {
                    that._active = true;
                }

                if (e.ctrlKey) {
                    if (key == keys.RIGHT && !isRtl || key == keys.LEFT && isRtl) {
                        that.navigateToFuture();
                        prevent = true;
                    } else if (key == keys.LEFT && !isRtl || key == keys.RIGHT && isRtl) {
                        that.navigateToPast();
                        prevent = true;
                    } else if (key == keys.UP) {
                        that.navigateUp();
                        prevent = true;
                    } else if (key == keys.DOWN) {
                        that._click($(that._cell[0].firstChild));
                        prevent = true;
                    }
                } else {
                    if (key == keys.RIGHT && !isRtl || key == keys.LEFT && isRtl) {
                        value = 1;
                        prevent = true;
                    } else if (key == keys.LEFT && !isRtl || key == keys.RIGHT && isRtl) {
                        value = -1;
                        prevent = true;
                    } else if (key == keys.UP) {
                        value = index === 0 ? -7 : -4;
                        prevent = true;
                    } else if (key == keys.DOWN) {
                        value = index === 0 ? 7 : 4;
                        prevent = true;
                    } else if (key == keys.ENTER) {
                        that._click($(that._cell[0].firstChild));
                        prevent = true;
                    } else if (key == keys.HOME || key == keys.END) {
                        method = key == keys.HOME ? "first" : "last";
                        temp = view[method](currentValue);
                        currentValue = new DATE(temp.getFullYear(), temp.getMonth(), temp.getDate(), currentValue.getHours(), currentValue.getMinutes(), currentValue.getSeconds(), currentValue.getMilliseconds());
                        prevent = true;
                    } else if (key == keys.PAGEUP) {
                        prevent = true;
                        that.navigateToPast();
                    } else if (key == keys.PAGEDOWN) {
                        prevent = true;
                        that.navigateToFuture();
                    }

                    if (value || method) {
                        if (!method) {
                            view.setDate(currentValue, value);
                        }

                        that._focus(restrictValue(currentValue, options.min, options.max));
                    }
                }

                if (prevent) {
                    e.preventDefault();
                }

                return that._current;
            },

            _animate: function (options) {
                var that = this,
                from = options.from,
                to = options.to,
                active = that._active;

                if (!from) {
                    to.insertAfter(that.element[0].firstChild);
                    that._bindTable(to);
                } else if (from.parent().data("animating")) {
                    from.off(ns);
                    from.parent().kendoStop(true, true).remove();
                    from.remove();

                    to.insertAfter(that.element[0].firstChild);
                    that._focusView(active);
                } else if (!from.is(":visible") || that.options.animation === false) {
                    to.insertAfter(from);
                    from.off(ns).remove();

                    that._focusView(active);
                } else {
                    that[options.vertical ? "_vertical" : "_horizontal"](from, to, options.future);
                }
            },

            _horizontal: function (from, to, future) {
                var that = this,
                active = that._active,
                horizontal = that.options.animation.horizontal,
                effects = horizontal.effects,
                viewWidth = from.outerWidth();

                if (effects && effects.indexOf(SLIDE) != -1) {
                    from.add(to).css({ width: viewWidth });

                    from.wrap("<div/>");

                    that._focusView(active, from);

                    from.parent()
                    .css({
                        position: "relative",
                        width: viewWidth * 2,
                        "float": LEFT,
                        "margin-left": future ? 0 : -viewWidth
                    });

                    to[future ? "insertAfter" : "insertBefore"](from);

                    extend(horizontal, {
                        effects: SLIDE + ":" + (future ? "right" : LEFT),
                        complete: function () {
                            from.off(ns).remove();
                            that._oldTable = null;

                            to.unwrap();

                            that._focusView(active);

                        }
                    });

                    from.parent().kendoStop(true, true).kendoAnimate(horizontal);
                }
            },

            _vertical: function (from, to) {
                var that = this,
                vertical = that.options.animation.vertical,
                effects = vertical.effects,
                active = that._active, //active state before from's blur
                cell, position;

                if (effects && effects.indexOf("zoom") != -1) {
                    to.css({
                        position: "absolute",
                        top: from.prev().outerHeight(),
                        left: 0
                    }).insertBefore(from);

                    if (transitionOrigin) {
                        cell = that._cellByDate(that._view.toDateString(that._current));
                        position = cell.position();
                        position = (position.left + parseInt(cell.width() / 2, 10)) + "px" + " " + (position.top + parseInt(cell.height() / 2, 10) + "px");
                        to.css(transitionOrigin, position);
                    }

                    from.kendoStop(true, true).kendoAnimate({
                        effects: "fadeOut",
                        duration: 600,
                        complete: function () {
                            from.off(ns).remove();
                            that._oldTable = null;

                            to.css({
                                position: "static",
                                top: 0,
                                left: 0
                            });

                            that._focusView(active);
                        }
                    });

                    to.kendoStop(true, true).kendoAnimate(vertical);
                }
            },

            _cellByDate: function (value) {
                return this._table.find("td:not(." + OTHERMONTH + ")")
                       .filter(function () {
                           return $(this.firstChild).attr(kendo.attr(VALUE)) === value;
                       });
            },

            _class: function (className, value) {
                var that = this,
                id = that._cellID,
                cell = that._cell;

                if (cell) {
                    cell.removeAttr(ARIA_SELECTED)
                    .removeAttr("aria-label")
                    .removeAttr(ID);
                }

                cell = that._table
                       .find("td:not(." + OTHERMONTH + ")")
                       .removeClass(className)
                       .filter(function () {
                           return $(this.firstChild).attr(kendo.attr(VALUE)) === value;
                       })
                       .attr(ARIA_SELECTED, true);

                if (className === FOCUSED && !that._active && that.options.focusOnNav !== false) {
                    className = "";
                }

                cell.addClass(className);

                if (cell[0]) {
                    that._cell = cell;
                }

                if (id) {
                    cell.attr(ID, id);
                    that._table.removeAttr("aria-activedescendant").attr("aria-activedescendant", id);
                }
            },

            _bindTable: function (table) {
                table
                .on(FOCUS_WITH_NS, this._addClassProxy)
                .on(BLUR, this._removeClassProxy);
            },

            _click: function (link) {
                var that = this,
                options = that.options,
                currentValue = new Date(+that._current),
                value = link.attr(kendo.attr(VALUE)).split("/");

                //Safari cannot create correctly date from "1/1/2090"
                value = new DATE(value[0], value[1], value[2]);
                adjustDST(value, 0);

                that._view.setDate(currentValue, value);

                that.navigateDown(restrictValue(currentValue, options.min, options.max));
            },

            _focus: function (value) {
                var that = this,
                view = that._view;

                if (view.compare(value, that._current) !== 0) {
                    that.navigate(value);
                } else {
                    that._current = value;
                    that._class(FOCUSED, view.toDateString(value));
                }
            },

            _focusView: function (active, table) {
                if (active) {
                    this.focus(table);
                }
            },

            _footer: function (template) {
                var that = this,
                today = getToday(),
                element = that.element,
                footer = element.find(".k-footer");

                if (!template) {
                    that._toggle(false);
                    footer.hide();
                    return;
                }

                if (!footer[0]) {
                    footer = $('<div class="k-footer"><a href="#" class="k-link k-nav-today"></a></div>').appendTo(element);
                }

                that._today = footer.show()
                                .find(".k-link")
                                .html(template(today))
                                .attr("title", kendo.toString(today, "D", that.options.culture));

                that._toggle();
            },

            _header: function () {
                var that = this,
            element = that.element,
            links;

                if (!element.find(".k-header")[0]) {
                    element.html('<div class="k-header">' +
                             '<a href="#" role="button" class="k-link k-nav-prev"><span class="k-icon k-i-arrow-w"></span></a>' +
                             '<a href="#" role="button" aria-live="assertive" aria-atomic="true" class="k-link k-nav-fast"></a>' +
                             '<a href="#" role="button" class="k-link k-nav-next"><span class="k-icon k-i-arrow-e"></span></a>' +
                             '</div>');
                }

                links = element.find(".k-link")
                           .on(MOUSEENTER_WITH_NS + " " + MOUSELEAVE + " " + FOCUS_WITH_NS + " " + BLUR, mousetoggle)
                           .click(false);

                that._title = links.eq(1).on(CLICK, function () { that._active = that.options.focusOnNav !== false; that.navigateUp(); });
                that[PREVARROW] = links.eq(0).on(CLICK, function () { that._active = that.options.focusOnNav !== false; that.navigateToPast(); });
                that[NEXTARROW] = links.eq(2).on(CLICK, function () { that._active = that.options.focusOnNav !== false; that.navigateToFuture(); });
            },

            _navigate: function (arrow, modifier) {
                var that = this,
                index = that._index + 1,
                currentValue = new DATE(+that._current);

                arrow = that[arrow];

                if (!arrow.hasClass(DISABLED)) {
                    if (index > 3) {
                        currentValue.setFullYear(currentValue.getFullYear() + 100 * modifier);
                    } else {
                        calendar.views[index].setDate(currentValue, modifier);
                    }

                    that.navigate(currentValue);
                }
            },

            _option: function (option, value) {
                var that = this,
                options = that.options,
                currentValue = that._value || that._current,
                isBigger;

                if (value === undefined) {
                    return options[option];
                }

                value = parse(value, options.format, options.culture);

                if (!value) {
                    return;
                }

                options[option] = new DATE(+value);

                if (option === MIN) {
                    isBigger = value > currentValue;
                } else {
                    isBigger = currentValue > value;
                }

                if (isBigger || isEqualMonth(currentValue, value)) {
                    if (isBigger) {
                        that._value = null;
                    }
                    that._changeView = true;
                }

                if (!that._changeView) {
                    that._changeView = !!(options.month.content || options.month.empty);
                }

                that.navigate(that._value);

                that._toggle();
            },

            _toggle: function (toggle) {
                var that = this,
                options = that.options,
                link = that._today;

                if (toggle === undefined) {
                    toggle = isInRange(getToday(), options.min, options.max);
                }

                if (link) {
                    link.off(CLICK);

                    if (toggle) {
                        link.addClass(TODAY)
                        .removeClass(DISABLED)
                        .on(CLICK, proxy(that._todayClick, that));
                    } else {
                        link.removeClass(TODAY)
                        .addClass(DISABLED)
                        .on(CLICK, prevent);
                    }
                }
            },

            _todayClick: function (e) {
                var that = this,
                depth = views[that.options.depth],
                today = getToday();

                e.preventDefault();

                if (that._view.compare(that._current, today) === 0 && that._index == depth) {
                    that._changeView = false;
                }

                that._value = today;
                that.navigate(today, depth);

                that.trigger(CHANGE);
            },

            _templates: function () {
                var that = this,
                options = that.options,
                footer = options.footer,
                month = options.month,
                content = month.content,
                empty = month.empty;

                that.month = {
                    content: template('<td#=data.cssClass# role="gridcell"><a tabindex="-1" class="k-link#=data.linkClass#" href="#=data.url#" ' + kendo.attr("value") + '="#=data.dateString#" title="#=data.title#">' + (content || "#=data.value#") + '</a></td>', { useWithBlock: !!content }),
                    empty: template('<td role="gridcell">' + (empty || "&nbsp;") + "</td>", { useWithBlock: !!empty })
                };

                that.footer = footer !== false ? template(footer || '#= kendo.toString(data,"D","' + options.culture + '") #', { useWithBlock: false }) : null;
            }
        });

        ui.plugin(Calendar);

        var calendar = {
            firstDayOfMonth: function (date) {
                return new DATE(
                date.getFullYear(),
                date.getMonth(),
                1
            );
            },

            firstVisibleDay: function (date, calendarInfo) {
                calendarInfo = calendarInfo || kendo.culture().calendar;

                var firstDay = calendarInfo.firstDay,
            firstVisibleDay = new DATE(date.getFullYear(), date.getMonth(), 0, date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());

                while (firstVisibleDay.getDay() != firstDay) {
                    calendar.setTime(firstVisibleDay, -1 * MS_PER_DAY);
                }

                return firstVisibleDay;
            },

            setTime: function (date, time) {
                var tzOffsetBefore = date.getTimezoneOffset(),
            resultDATE = new DATE(date.getTime() + time),
            tzOffsetDiff = resultDATE.getTimezoneOffset() - tzOffsetBefore;

                date.setTime(resultDATE.getTime() + tzOffsetDiff * MS_PER_MINUTE);
            },
            views: [{
                name: MONTH,
                title: function (date, min, max, culture) {
                    return getCalendarInfo(culture).months.names[date.getMonth()] + " " + date.getFullYear();
                },
                content: function (options) {
                    var that = this,
                idx = 0,
                min = options.min,
                max = options.max,
                date = options.date,
                dates = options.dates,
                format = options.format,
                culture = options.culture,
                navigateUrl = options.url,
                hasUrl = navigateUrl && dates[0],
                currentCalendar = getCalendarInfo(culture),
                firstDayIdx = currentCalendar.firstDay,
                days = currentCalendar.days,
                names = shiftArray(days.names, firstDayIdx),
                shortNames = shiftArray(days.namesShort, firstDayIdx),
                start = calendar.firstVisibleDay(date, currentCalendar),
                firstDayOfMonth = that.first(date),
                lastDayOfMonth = that.last(date),
                toDateString = that.toDateString,
                today = new DATE(),
                html = '<table tabindex="0" role="grid" class="k-content" cellspacing="0"><thead><tr role="row">';

                    for (; idx < 7; idx++) {
                        html += '<th scope="col" title="' + names[idx] + '">' + shortNames[idx] + '</th>';
                    }

                    today = new DATE(today.getFullYear(), today.getMonth(), today.getDate());
                    adjustDST(today, 0);
                    today = +today;

                    return view({
                        cells: 42,
                        perRow: 7,
                        html: html += '</tr></thead><tbody><tr role="row">',
                        start: new DATE(start.getFullYear(), start.getMonth(), start.getDate()),
                        min: new DATE(min.getFullYear(), min.getMonth(), min.getDate()),
                        max: new DATE(max.getFullYear(), max.getMonth(), max.getDate()),
                        content: options.content,
                        empty: options.empty,
                        setter: that.setDate,
                        build: function (date) {
                            var cssClass = [],
                            day = date.getDay(),
                            linkClass = "",
                            url = "#";

                            if (date < firstDayOfMonth || date > lastDayOfMonth) {
                                cssClass.push(OTHERMONTH);
                            }

                            if (+date === today) {
                                cssClass.push("k-today");
                            }

                            if (day === 0 || day === 6) {
                                cssClass.push("k-weekend");
                            }

                            if (hasUrl && inArray(+date, dates)) {
                                url = navigateUrl.replace("{0}", kendo.toString(date, format, culture));
                                linkClass = " k-action-link";
                            }

                            return {
                                date: date,
                                dates: dates,
                                ns: kendo.ns,
                                title: kendo.toString(date, "D", culture),
                                value: date.getDate(),
                                dateString: toDateString(date),
                                cssClass: cssClass[0] ? ' class="' + cssClass.join(" ") + '"' : "",
                                linkClass: linkClass,
                                url: url
                            };
                        }
                    });
                },
                first: function (date) {
                    return calendar.firstDayOfMonth(date);
                },
                last: function (date) {
                    var last = new DATE(date.getFullYear(), date.getMonth() + 1, 0),
                    first = calendar.firstDayOfMonth(date),
                    timeOffset = Math.abs(last.getTimezoneOffset() - first.getTimezoneOffset());

                    if (timeOffset) {
                        last.setHours(first.getHours() + (timeOffset / 60));
                    }

                    return last;
                },
                compare: function (date1, date2) {
                    var result,
                month1 = date1.getMonth(),
                year1 = date1.getFullYear(),
                month2 = date2.getMonth(),
                year2 = date2.getFullYear();

                    if (year1 > year2) {
                        result = 1;
                    } else if (year1 < year2) {
                        result = -1;
                    } else {
                        result = month1 == month2 ? 0 : month1 > month2 ? 1 : -1;
                    }

                    return result;
                },
                setDate: function (date, value) {
                    var hours = date.getHours();
                    if (value instanceof DATE) {
                        date.setFullYear(value.getFullYear(), value.getMonth(), value.getDate());
                    } else {
                        calendar.setTime(date, value * MS_PER_DAY);
                    }
                    adjustDST(date, hours);
                },
                toDateString: function (date) {
                    return date.getFullYear() + "/" + date.getMonth() + "/" + date.getDate();
                }
            },
        {
            name: "year",
            title: function (date) {
                return date.getFullYear();
            },
            content: function (options) {
                var namesAbbr = getCalendarInfo(options.culture).months.namesAbbr,
                toDateString = this.toDateString,
                min = options.min,
                max = options.max;

                return view({
                    min: new DATE(min.getFullYear(), min.getMonth(), 1),
                    max: new DATE(max.getFullYear(), max.getMonth(), 1),
                    start: new DATE(options.date.getFullYear(), 0, 1),
                    setter: this.setDate,
                    build: function (date) {
                        return {
                            value: namesAbbr[date.getMonth()],
                            ns: kendo.ns,
                            dateString: toDateString(date),
                            cssClass: ""
                        };
                    }
                });
            },
            first: function (date) {
                return new DATE(date.getFullYear(), 0, date.getDate());
            },
            last: function (date) {
                return new DATE(date.getFullYear(), 11, date.getDate());
            },
            compare: function (date1, date2) {
                return compare(date1, date2);
            },
            setDate: function (date, value) {
                var month,
                    hours = date.getHours();

                if (value instanceof DATE) {
                    month = value.getMonth();

                    date.setFullYear(value.getFullYear(), month, date.getDate());

                    if (month !== date.getMonth()) {
                        date.setDate(0);
                    }
                } else {
                    month = date.getMonth() + value;

                    date.setMonth(month);

                    if (month > 11) {
                        month -= 12;
                    }

                    if (month > 0 && date.getMonth() != month) {
                        date.setDate(0);
                    }
                }

                adjustDST(date, hours);
            },
            toDateString: function (date) {
                return date.getFullYear() + "/" + date.getMonth() + "/1";
            }
        },
        {
            name: "decade",
            title: function (date, min, max) {
                return title(date, min, max, 10);
            },
            content: function (options) {
                var year = options.date.getFullYear(),
                toDateString = this.toDateString;

                return view({
                    start: new DATE(year - year % 10 - 1, 0, 1),
                    min: new DATE(options.min.getFullYear(), 0, 1),
                    max: new DATE(options.max.getFullYear(), 0, 1),
                    setter: this.setDate,
                    build: function (date, idx) {
                        return {
                            value: date.getFullYear(),
                            ns: kendo.ns,
                            dateString: toDateString(date),
                            cssClass: idx === 0 || idx == 11 ? OTHERMONTHCLASS : ""
                        };
                    }
                });
            },
            first: function (date) {
                var year = date.getFullYear();
                return new DATE(year - year % 10, date.getMonth(), date.getDate());
            },
            last: function (date) {
                var year = date.getFullYear();
                return new DATE(year - year % 10 + 9, date.getMonth(), date.getDate());
            },
            compare: function (date1, date2) {
                return compare(date1, date2, 10);
            },
            setDate: function (date, value) {
                setDate(date, value, 1);
            },
            toDateString: function (date) {
                return date.getFullYear() + "/0/1";
            }
        },
        {
            name: CENTURY,
            title: function (date, min, max) {
                return title(date, min, max, 100);
            },
            content: function (options) {
                var year = options.date.getFullYear(),
                min = options.min.getFullYear(),
                max = options.max.getFullYear(),
                toDateString = this.toDateString,
                minYear = min,
                maxYear = max;

                minYear = minYear - minYear % 10;
                maxYear = maxYear - maxYear % 10;

                if (maxYear - minYear < 10) {
                    maxYear = minYear + 9;
                }

                return view({
                    start: new DATE(year - year % 100 - 10, 0, 1),
                    min: new DATE(minYear, 0, 1),
                    max: new DATE(maxYear, 0, 1),
                    setter: this.setDate,
                    build: function (date, idx) {
                        var start = date.getFullYear(),
                            end = start + 9;

                        if (start < min) {
                            start = min;
                        }

                        if (end > max) {
                            end = max;
                        }

                        return {
                            ns: kendo.ns,
                            value: start + " - " + end,
                            dateString: toDateString(date),
                            cssClass: idx === 0 || idx == 11 ? OTHERMONTHCLASS : ""
                        };
                    }
                });
            },
            first: function (date) {
                var year = date.getFullYear();
                return new DATE(year - year % 100, date.getMonth(), date.getDate());
            },
            last: function (date) {
                var year = date.getFullYear();
                return new DATE(year - year % 100 + 99, date.getMonth(), date.getDate());
            },
            compare: function (date1, date2) {
                return compare(date1, date2, 100);
            },
            setDate: function (date, value) {
                setDate(date, value, 10);
            },
            toDateString: function (date) {
                var year = date.getFullYear();
                return (year - year % 10) + "/0/1";
            }
        }]
        };

        function title(date, min, max, modular) {
            var start = date.getFullYear(),
            minYear = min.getFullYear(),
            maxYear = max.getFullYear(),
            end;

            start = start - start % modular;
            end = start + (modular - 1);

            if (start < minYear) {
                start = minYear;
            }
            if (end > maxYear) {
                end = maxYear;
            }

            return start + "-" + end;
        }

        function view(options) {
            var idx = 0,
            data,
            min = options.min,
            max = options.max,
            start = options.start,
            setter = options.setter,
            build = options.build,
            length = options.cells || 12,
            cellsPerRow = options.perRow || 4,
            content = options.content || cellTemplate,
            empty = options.empty || emptyCellTemplate,
            html = options.html || '<table tabindex="0" role="grid" class="k-content k-meta-view" cellspacing="0"><tbody><tr role="row">';

            for (; idx < length; idx++) {
                if (idx > 0 && idx % cellsPerRow === 0) {
                    html += '</tr><tr role="row">';
                }

                data = build(start, idx);

                html += isInRange(start, min, max) ? content(data) : empty(data);

                setter(start, 1);
            }

            return html + "</tr></tbody></table>";
        }

        function compare(date1, date2, modifier) {
            var year1 = date1.getFullYear(),
            start = date2.getFullYear(),
            end = start,
            result = 0;

            if (modifier) {
                start = start - start % modifier;
                end = start - start % modifier + modifier - 1;
            }

            if (year1 > end) {
                result = 1;
            } else if (year1 < start) {
                result = -1;
            }

            return result;
        }

        function getToday() {
            var today = new DATE();
            return new DATE(today.getFullYear(), today.getMonth(), today.getDate());
        }

        function restrictValue(value, min, max) {
            var today = getToday();

            if (value) {
                today = new DATE(+value);
            }

            if (min > today) {
                today = new DATE(+min);
            } else if (max < today) {
                today = new DATE(+max);
            }
            return today;
        }

        function isInRange(date, min, max) {
            return +date >= +min && +date <= +max;
        }

        function shiftArray(array, idx) {
            return array.slice(idx).concat(array.slice(0, idx));
        }

        function setDate(date, value, multiplier) {
            value = value instanceof DATE ? value.getFullYear() : date.getFullYear() + multiplier * value;
            date.setFullYear(value);
        }

        function mousetoggle(e) {
            $(this).toggleClass(HOVER, MOUSEENTER.indexOf(e.type) > -1 || e.type == FOCUS);
        }

        function prevent(e) {
            e.preventDefault();
        }

        function getCalendarInfo(culture) {
            return getCulture(culture).calendars.standard;
        }

        function normalize(options) {
            var start = views[options.start],
            depth = views[options.depth],
            culture = getCulture(options.culture);

            options.format = extractFormat(options.format || culture.calendars.standard.patterns.d);

            if (isNaN(start)) {
                start = 0;
                options.start = MONTH;
            }

            if (depth === undefined || depth > start) {
                options.depth = MONTH;
            }

            if (!options.dates) {
                options.dates = [];
            }
        }

        function makeUnselectable(element) {
            if (isIE8) {
                element.find("*").attr("unselectable", "on");
            }
        }

        function inArray(date, dates) {
            for (var i = 0, length = dates.length; i < length; i++) {
                if (date === +dates[i]) {
                    return true;
                }
            }
            return false;
        }

        function isEqualDatePart(value1, value2) {
            if (value1) {
                return value1.getFullYear() === value2.getFullYear() &&
                   value1.getMonth() === value2.getMonth() &&
                   value1.getDate() === value2.getDate();
            }

            return false;
        }

        function isEqualMonth(value1, value2) {
            if (value1) {
                return value1.getFullYear() === value2.getFullYear() &&
                   value1.getMonth() === value2.getMonth();
            }

            return false;
        }

        calendar.isEqualDatePart = isEqualDatePart;
        calendar.makeUnselectable = makeUnselectable;
        calendar.restrictValue = restrictValue;
        calendar.isInRange = isInRange;
        calendar.normalize = normalize;
        calendar.viewsEnum = views;

        kendo.calendar = calendar;
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
    ui = kendo.ui,
    Widget = ui.Widget,
    parse = kendo.parseDate,
    keys = kendo.keys,
    template = kendo.template,
    activeElement = kendo._activeElement,
    DIV = "<div />",
    SPAN = "<span />",
    ns = ".kendoDatePicker",
    CLICK = "click" + ns,
    OPEN = "open",
    CLOSE = "close",
    CHANGE = "change",
    DATEVIEW = "dateView",
    DISABLED = "disabled",
    READONLY = "readonly",
    DEFAULT = "k-state-default",
    FOCUSED = "k-state-focused",
    SELECTED = "k-state-selected",
    STATEDISABLED = "k-state-disabled",
    HOVER = "k-state-hover",
    KEYDOWN = "keydown" + ns,
    HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns,
    MOUSEDOWN = "mousedown" + ns,
    ID = "id",
    MIN = "min",
    MAX = "max",
    MONTH = "month",
    ARIA_DISABLED = "aria-disabled",
    ARIA_EXPANDED = "aria-expanded",
    ARIA_HIDDEN = "aria-hidden",
    ARIA_READONLY = "aria-readonly",
    calendar = kendo.calendar,
    isInRange = calendar.isInRange,
    restrictValue = calendar.restrictValue,
    isEqualDatePart = calendar.isEqualDatePart,
    extend = $.extend,
    proxy = $.proxy,
    DATE = Date;

        function normalize(options) {
            var parseFormats = options.parseFormats,
            format = options.format;

            calendar.normalize(options);

            parseFormats = $.isArray(parseFormats) ? parseFormats : [parseFormats];
            if ($.inArray(format, parseFormats) === -1) {
                parseFormats.splice(0, 0, options.format);
            }

            options.parseFormats = parseFormats;
        }

        function preventDefault(e) {
            e.preventDefault();
        }

        var DateView = function (options) {
            var that = this, id,
            body = document.body,
            div = $(DIV).attr(ARIA_HIDDEN, "true")
                        .addClass("k-calendar-container")
                        .appendTo(body);

            that.options = options = options || {};
            id = options.id;

            if (id) {
                id += "_dateview";

                div.attr(ID, id);
                that._dateViewID = id;
            }

            that.popup = new ui.Popup(div, extend(options.popup, options, { name: "Popup", isRtl: kendo.support.isRtl(options.anchor) }));
            that.div = div;

            that.value(options.value);
        };

        DateView.prototype = {
            _calendar: function () {
                var that = this;
                var calendar = that.calendar;
                var options = that.options;
                var div;

                if (!calendar) {
                    div = $(DIV).attr(ID, kendo.guid())
                            .appendTo(that.popup.element)
                            .on(MOUSEDOWN, preventDefault)
                            .on(CLICK, "td:has(.k-link)", proxy(that._click, that));

                    that.calendar = calendar = new ui.Calendar(div);
                    that._setOptions(options);

                    kendo.calendar.makeUnselectable(calendar.element);

                    calendar.navigate(that._value || that._current, options.start);

                    that.value(that._value);
                }
            },

            _setOptions: function (options) {
                this.calendar.setOptions({
                    focusOnNav: false,
                    change: options.change,
                    culture: options.culture,
                    dates: options.dates,
                    depth: options.depth,
                    footer: options.footer,
                    format: options.format,
                    max: options.max,
                    min: options.min,
                    month: options.month,
                    start: options.start
                });
            },

            setOptions: function (options) {
                var old = this.options;

                this.options = extend(old, options, {
                    change: old.change,
                    close: old.close,
                    open: old.open
                });

                if (this.calendar) {
                    this._setOptions(this.options);
                }
            },

            destroy: function () {
                this.popup.destroy();
            },

            open: function () {
                var that = this;

                that._calendar();
                that.popup.open();
            },

            close: function () {
                this.popup.close();
            },

            min: function (value) {
                this._option(MIN, value);
            },

            max: function (value) {
                this._option(MAX, value);
            },

            toggle: function () {
                var that = this;

                that[that.popup.visible() ? CLOSE : OPEN]();
            },

            move: function (e) {
                var that = this,
                key = e.keyCode,
                calendar = that.calendar,
                selectIsClicked = e.ctrlKey && key == keys.DOWN || key == keys.ENTER;

                if (key == keys.ESC) {
                    that.close();
                    return;
                }

                if (e.altKey) {
                    if (key == keys.DOWN) {
                        that.open();
                        e.preventDefault();
                    } else if (key == keys.UP) {
                        that.close();
                        e.preventDefault();
                    }
                    return;
                }

                if (!that.popup.visible()) {
                    return;
                }

                if (selectIsClicked && calendar._cell.hasClass(SELECTED)) {
                    that.close();
                    e.preventDefault();
                    return;
                }

                that._current = calendar._move(e);
            },

            current: function (date) {
                this._current = date;
                this.calendar._focus(date);
            },

            value: function (value) {
                var that = this,
                calendar = that.calendar,
                options = that.options;

                that._value = value;
                that._current = new DATE(+restrictValue(value, options.min, options.max));

                if (calendar) {
                    calendar.value(value);
                }
            },

            _click: function (e) {
                if (e.currentTarget.className.indexOf(SELECTED) !== -1) {
                    this.close();
                }
            },

            _option: function (option, value) {
                var that = this;
                var calendar = that.calendar;

                that.options[option] = value;

                if (calendar) {
                    calendar[option](value);
                }
            }
        };

        DateView.normalize = normalize;

        kendo.DateView = DateView;

        var DatePicker = Widget.extend({
            init: function (element, options) {
                var that = this,
                disabled,
                div;

                Widget.fn.init.call(that, element, options);
                element = that.element;
                options = that.options;

                options.min = parse(element.attr("min")) || parse(options.min);
                options.max = parse(element.attr("max")) || parse(options.max);

                normalize(options);

                that._wrapper();

                that.dateView = new DateView(extend({}, options, {
                    id: element.attr(ID),
                    anchor: that.wrapper,
                    change: function () {
                        // calendar is the current scope
                        that._change(this.value());
                        that.close();
                    },
                    close: function (e) {
                        if (that.trigger(CLOSE)) {
                            e.preventDefault();
                        } else {
                            element.attr(ARIA_EXPANDED, false);
                            div.attr(ARIA_HIDDEN, true);
                        }
                    },
                    open: function (e) {
                        var options = that.options,
                        date;

                        if (that.trigger(OPEN)) {
                            e.preventDefault();
                        } else {
                            if (that.element.val() !== that._oldText) {
                                date = parse(element.val(), options.parseFormats, options.culture);

                                that.dateView[date ? "current" : "value"](date);
                            }

                            element.attr(ARIA_EXPANDED, true);
                            div.attr(ARIA_HIDDEN, false);

                            that._updateARIA(date);

                        }
                    }
                }));
                div = that.dateView.div;

                that._icon();

                try {
                    element[0].setAttribute("type", "text");
                } catch (e) {
                    element[0].type = "text";
                }

                element
                .addClass("k-input")
                .attr({
                    role: "combobox",
                    "aria-expanded": false,
                    "aria-owns": that.dateView._dateViewID
                });

                that._reset();
                that._template();

                disabled = element.is("[disabled]");
                if (disabled) {
                    that.enable(false);
                } else {
                    that.readonly(element.is("[readonly]"));
                }

                that._old = that._update(options.value || that.element.val());
                that._oldText = element.val();

                kendo.notify(that);
            },
            events: [
        OPEN,
        CLOSE,
        CHANGE],
            options: {
                name: "DatePicker",
                value: null,
                footer: "",
                format: "",
                culture: "",
                parseFormats: [],
                min: new Date(1900, 0, 1),
                max: new Date(2099, 11, 31),
                start: MONTH,
                depth: MONTH,
                animation: {},
                month: {},
                dates: [],
                ARIATemplate: 'Current focused date is #=kendo.toString(data.current, "D")#'
            },

            setOptions: function (options) {
                var that = this;
                var value = that._value;

                Widget.fn.setOptions.call(that, options);

                options = that.options;

                options.min = parse(options.min);
                options.max = parse(options.max);

                normalize(options);

                that.dateView.setOptions(options);

                if (value) {
                    that.element.val(kendo.toString(value, options.format, options.culture));
                    that._updateARIA(value);
                }
            },

            _editable: function (options) {
                var that = this,
                icon = that._dateIcon.off(ns),
                element = that.element.off(ns),
                wrapper = that._inputWrapper.off(ns),
                readonly = options.readonly,
                disable = options.disable;

                if (!readonly && !disable) {
                    wrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED)
                    .on(HOVEREVENTS, that._toggleHover);

                    element.removeAttr(DISABLED)
                       .removeAttr(READONLY)
                       .attr(ARIA_DISABLED, false)
                       .attr(ARIA_READONLY, false)
                       .on("keydown" + ns, proxy(that._keydown, that))
                       .on("focusout" + ns, proxy(that._blur, that))
                       .on("focus" + ns, function () {
                           that._inputWrapper.addClass(FOCUSED);
                       });

                    icon.on(CLICK, proxy(that._click, that))
                   .on(MOUSEDOWN, preventDefault);
                } else {
                    wrapper
                    .addClass(disable ? STATEDISABLED : DEFAULT)
                    .removeClass(disable ? DEFAULT : STATEDISABLED);

                    element.attr(DISABLED, disable)
                       .attr(READONLY, readonly)
                       .attr(ARIA_DISABLED, disable)
                       .attr(ARIA_READONLY, readonly);
                }
            },

            readonly: function (readonly) {
                this._editable({
                    readonly: readonly === undefined ? true : readonly,
                    disable: false
                });
            },

            enable: function (enable) {
                this._editable({
                    readonly: false,
                    disable: !(enable = enable === undefined ? true : enable)
                });
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.dateView.destroy();

                that.element.off(ns);
                that._dateIcon.off(ns);
                that._inputWrapper.off(ns);

                if (that._form) {
                    that._form.off("reset", that._resetHandler);
                }
            },

            open: function () {
                this.dateView.open();
            },

            close: function () {
                this.dateView.close();
            },

            min: function (value) {
                return this._option(MIN, value);
            },

            max: function (value) {
                return this._option(MAX, value);
            },

            value: function (value) {
                var that = this;

                if (value === undefined) {
                    return that._value;
                }

                that._old = that._update(value);

                if (that._old === null) {
                    that.element.val("");
                }

                that._oldText = that.element.val();
            },

            _toggleHover: function (e) {
                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
            },

            _blur: function () {
                var that = this,
                value = that.element.val();

                that.close();
                if (value !== that._oldText) {
                    that._change(value);
                }

                that._inputWrapper.removeClass(FOCUSED);
            },

            _click: function () {
                var that = this,
                element = that.element;

                that.dateView.toggle();

                if (!kendo.support.touch && element[0] !== activeElement()) {
                    element.focus();
                }
            },

            _change: function (value) {
                var that = this;

                value = that._update(value);

                if (+that._old != +value) {
                    that._old = value;
                    that._oldText = that.element.val();

                    // trigger the DOM change event so any subscriber gets notified
                    that.element.trigger(CHANGE);

                    that.trigger(CHANGE);
                }
            },

            _keydown: function (e) {
                var that = this,
                dateView = that.dateView,
                value = that.element.val();

                if (!dateView.popup.visible() && e.keyCode == keys.ENTER && value !== that._oldText) {
                    that._change(value);
                } else {
                    dateView.move(e);
                    that._updateARIA(dateView._current);
                }
            },

            _icon: function () {
                var that = this,
                element = that.element,
                icon;

                icon = element.next("span.k-select");

                if (!icon[0]) {
                    icon = $('<span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-calendar">select</span></span>').insertAfter(element);
                }

                that._dateIcon = icon.attr({
                    "role": "button",
                    "aria-controls": that.dateView._dateViewID
                });
            },

            _option: function (option, value) {
                var that = this,
                options = that.options;

                if (value === undefined) {
                    return options[option];
                }

                value = parse(value, options.parseFormats, options.culture);

                if (!value) {
                    return;
                }

                options[option] = new DATE(+value);
                that.dateView[option](value);
            },

            _update: function (value) {
                var that = this,
                options = that.options,
                min = options.min,
                max = options.max,
                current = that._value,
                date = parse(value, options.parseFormats, options.culture),
                isSameType = (date === null && current === null) || (date instanceof Date && current instanceof Date),
                formattedValue;

                if (+date === +current && isSameType) {
                    formattedValue = kendo.toString(date, options.format, options.culture);

                    if (formattedValue !== value) {
                        that.element.val(date === null ? value : formattedValue);
                    }

                    return date;
                }

                if (date !== null && isEqualDatePart(date, min)) {
                    date = restrictValue(date, min, max);
                } else if (!isInRange(date, min, max)) {
                    date = null;
                }

                that._value = date;
                that.dateView.value(date);
                that.element.val(date ? kendo.toString(date, options.format, options.culture) : value);
                that._updateARIA(date);

                return date;
            },

            _wrapper: function () {
                var that = this,
                element = that.element,
                wrapper;

                wrapper = element.parents(".k-datepicker");

                if (!wrapper[0]) {
                    wrapper = element.wrap(SPAN).parent().addClass("k-picker-wrap k-state-default");
                    wrapper = wrapper.wrap(SPAN).parent();
                }

                wrapper[0].style.cssText = element[0].style.cssText;
                element.css({
                    width: "100%",
                    height: element[0].style.height
                });

                that.wrapper = wrapper.addClass("k-widget k-datepicker k-header")
                                  .addClass(element[0].className);

                that._inputWrapper = $(wrapper[0].firstChild);
            },

            _reset: function () {
                var that = this,
                element = that.element,
                formId = element.attr("form"),
                form = formId ? $("#" + formId) : element.closest("form");

                if (form[0]) {
                    that._resetHandler = function () {
                        that.value(element[0].defaultValue);
                    };

                    that._form = form.on("reset", that._resetHandler);
                }
            },

            _template: function () {
                this._ariaTemplate = template(this.options.ARIATemplate);
            },

            _updateARIA: function (date) {
                var cell;
                var that = this;
                var calendar = that.dateView.calendar;

                that.element.removeAttr("aria-activedescendant");

                if (calendar) {
                    cell = calendar._cell;
                    cell.attr("aria-label", that._ariaTemplate({ current: date || calendar.current() }));

                    that.element.attr("aria-activedescendant", cell.attr("id"));
                }
            }
        });

        ui.plugin(DatePicker);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        support = kendo.support,
        caret = kendo.caret,
        activeElement = kendo._activeElement,
        placeholderSupported = support.placeholder,
        ui = kendo.ui,
        List = ui.List,
        keys = kendo.keys,
        DataSource = kendo.data.DataSource,
        ARIA_DISABLED = "aria-disabled",
        ARIA_READONLY = "aria-readonly",
        DEFAULT = "k-state-default",
        DISABLED = "disabled",
        READONLY = "readonly",
        FOCUSED = "k-state-focused",
        SELECTED = "k-state-selected",
        STATEDISABLED = "k-state-disabled",
        HOVER = "k-state-hover",
        ns = ".kendoAutoComplete",
        HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns,
        proxy = $.proxy;

        function indexOfWordAtCaret(caretIdx, text, separator) {
            return separator ? text.substring(0, caretIdx).split(separator).length - 1 : 0;
        }

        function wordAtCaret(caretIdx, text, separator) {
            return text.split(separator)[indexOfWordAtCaret(caretIdx, text, separator)];
        }

        function replaceWordAtCaret(caretIdx, text, word, separator) {
            var words = text.split(separator);

            words.splice(indexOfWordAtCaret(caretIdx, text, separator), 1, word);

            if (separator && words[words.length - 1] !== "") {
                words.push("");
            }

            return words.join(separator);
        }

        var AutoComplete = List.extend({
            init: function (element, options) {
                var that = this, wrapper;

                that.ns = ns;
                options = $.isArray(options) ? { dataSource: options} : options;

                List.fn.init.call(that, element, options);

                element = that.element;
                options = that.options;

                options.placeholder = options.placeholder || element.attr("placeholder");
                if (placeholderSupported) {
                    element.attr("placeholder", options.placeholder);
                }

                that._wrapper();
                that._loader();

                that._dataSource();
                that._ignoreCase();

                element[0].type = "text";
                wrapper = that.wrapper;

                that._popup();

                element
                .addClass("k-input")
                .on("keydown" + ns, proxy(that._keydown, that))
                .on("paste" + ns, proxy(that._search, that))
                .on("focus" + ns, function () {
                    that._prev = that._accessor();
                    that._placeholder(false);
                    wrapper.addClass(FOCUSED);
                })
                .on("focusout" + ns, function () {
                    that._change();
                    that._placeholder();
                    wrapper.removeClass(FOCUSED);
                })
                .attr({
                    autocomplete: "off",
                    role: "textbox",
                    "aria-haspopup": true
                });

                that._enable();

                that._old = that._accessor();

                if (element[0].id) {
                    element.attr("aria-owns", that.ul[0].id);
                }

                that._aria();

                that._placeholder();

                kendo.notify(that);
            },

            options: {
                name: "AutoComplete",
                enabled: true,
                suggest: false,
                template: "",
                dataTextField: "",
                minLength: 1,
                delay: 200,
                height: 200,
                filter: "startswith",
                ignoreCase: true,
                highlightFirst: false,
                separator: null,
                placeholder: "",
                animation: {},
                value: null
            },

            _dataSource: function () {
                var that = this;

                if (that.dataSource && that._refreshHandler) {
                    that._unbindDataSource();
                } else {
                    that._refreshHandler = proxy(that.refresh, that);
                    that._progressHandler = proxy(that._showBusy, that);
                }

                that.dataSource = DataSource.create(that.options.dataSource)
                .bind("change", that._refreshHandler)
                .bind("progress", that._progressHandler);
            },

            setDataSource: function (dataSource) {
                this.options.dataSource = dataSource;

                this._dataSource();
            },

            events: [
            "open",
            "close",
            "change",
            "select",
            "filtering",
            "dataBinding",
            "dataBound"
        ],

            setOptions: function (options) {
                List.fn.setOptions.call(this, options);

                this._template();
                this._accessors();
                this._aria();
            },

            _editable: function (options) {
                var that = this,
                element = that.element,
                wrapper = that.wrapper.off(ns),
                readonly = options.readonly,
                disable = options.disable;

                if (!readonly && !disable) {
                    wrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED)
                    .on(HOVEREVENTS, that._toggleHover);

                    element.removeAttr(DISABLED)
                       .removeAttr(READONLY)
                       .attr(ARIA_DISABLED, false)
                       .attr(ARIA_READONLY, false);
                } else {
                    wrapper
                    .addClass(disable ? STATEDISABLED : DEFAULT)
                    .removeClass(disable ? DEFAULT : STATEDISABLED);

                    element.attr(DISABLED, disable)
                       .attr(READONLY, readonly)
                       .attr(ARIA_DISABLED, disable)
                       .attr(ARIA_READONLY, readonly);
                }
            },

            close: function () {
                var that = this,
                current = that._current;

                if (current) {
                    current.removeClass(SELECTED);
                }

                that.current(null);
                that.popup.close();
            },

            destroy: function () {
                var that = this;

                that.element.off(ns);
                that.wrapper.off(ns);

                List.fn.destroy.call(that);
            },

            refresh: function () {
                var that = this,
            ul = that.ul[0],
            popup = that.popup,
            options = that.options,
            data = that._data(),
            length = data.length,
            isActive = that.element[0] === activeElement(),
            action;

                that._angularItems("cleanup");
                that.trigger("dataBinding");

                ul.innerHTML = kendo.render(that.template, data);

                that._height(length);

                if (popup.visible()) {
                    popup._position();
                }

                if (length) {
                    if (options.highlightFirst) {
                        that.current($(ul.firstChild));
                    }

                    if (options.suggest && isActive) {
                        that.suggest($(ul.firstChild));
                    }
                }

                if (that._open) {
                    that._open = false;
                    action = length ? "open" : "close";

                    if (that._typing && !isActive) {
                        action = "close";
                    }

                    popup[action]();
                    that._typing = undefined;
                }

                if (that._touchScroller) {
                    that._touchScroller.reset();
                }

                that._makeUnselectable();

                that._hideBusy();
                that._angularItems("compile");
                that.trigger("dataBound");
            },

            select: function (li) {
                this._select(li);
            },

            search: function (word) {
                var that = this,
            options = that.options,
            ignoreCase = options.ignoreCase,
            separator = options.separator,
            length;

                word = word || that._accessor();

                that._current = null;

                clearTimeout(that._typing);

                if (separator) {
                    word = wordAtCaret(caret(that.element)[0], word, separator);
                }

                length = word.length;

                if (!length || length >= options.minLength) {
                    that._open = true;

                    that._filterSource({
                        value: ignoreCase ? word.toLowerCase() : word,
                        operator: options.filter,
                        field: options.dataTextField,
                        ignoreCase: ignoreCase
                    });
                }
            },

            suggest: function (word) {
                var that = this,
                key = that._last,
                value = that._accessor(),
                element = that.element[0],
                caretIdx = caret(element)[0],
                separator = that.options.separator,
                words = value.split(separator),
                wordIndex = indexOfWordAtCaret(caretIdx, value, separator),
                selectionEnd = caretIdx,
                idx;

                if (key == keys.BACKSPACE || key == keys.DELETE) {
                    that._last = undefined;
                    return;
                }

                word = word || "";

                if (typeof word !== "string") {
                    idx = List.inArray(word[0], that.ul[0]);

                    if (idx > -1) {
                        word = that._text(that._data()[idx]);
                    } else {
                        word = "";
                    }
                }

                if (caretIdx <= 0) {
                    caretIdx = value.toLowerCase().indexOf(word.toLowerCase()) + 1;
                }

                idx = value.substring(0, caretIdx).lastIndexOf(separator);
                idx = idx > -1 ? caretIdx - (idx + separator.length) : caretIdx;
                value = words[wordIndex].substring(0, idx);

                if (word) {
                    idx = word.toLowerCase().indexOf(value.toLowerCase());
                    if (idx > -1) {
                        word = word.substring(idx + value.length);

                        selectionEnd = caretIdx + word.length;

                        value += word;
                    }

                    if (separator && words[words.length - 1] !== "") {
                        words.push("");
                    }

                }

                words[wordIndex] = value;

                that._accessor(words.join(separator || ""));

                if (element === activeElement()) {
                    caret(element, caretIdx, selectionEnd);
                }
            },

            value: function (value) {
                if (value !== undefined) {
                    this._accessor(value);
                    this._old = this._accessor();
                } else {
                    return this._accessor();
                }
            },

            _accessor: function (value) {
                var that = this,
                element = that.element[0];

                if (value !== undefined) {
                    element.value = value === null ? "" : value;
                    that._placeholder();
                } else {
                    value = element.value;

                    if (element.className.indexOf("k-readonly") > -1) {
                        if (value === that.options.placeholder) {
                            return "";
                        } else {
                            return value;
                        }
                    }

                    return value;
                }
            },

            _accept: function (li) {
                var element = this.element;

                this._focus(li);
                caret(element, element.val().length);
            },

            _keydown: function (e) {
                var that = this,
                ul = that.ul[0],
                key = e.keyCode,
                current = that._current,
                visible = that.popup.visible();

                that._last = key;

                if (key === keys.DOWN) {
                    if (visible) {
                        that._move(current ? current.next() : $(ul.firstChild));
                    }
                    e.preventDefault();
                } else if (key === keys.UP) {
                    if (visible) {
                        that._move(current ? current.prev() : $(ul.lastChild));
                    }
                    e.preventDefault();
                } else if (key === keys.ENTER || key === keys.TAB) {

                    if (key === keys.ENTER && that.popup.visible()) {
                        e.preventDefault();
                    }

                    that._accept(current);
                } else if (key === keys.ESC) {
                    if (that.popup.visible()) {
                        e.preventDefault();
                    }
                    that.close();
                } else {
                    that._search();
                }
            },

            _move: function (li) {
                var that = this;

                li = li[0] ? li : null;

                that.current(li);

                if (that.options.suggest) {
                    that.suggest(li);
                }
            },

            _hideBusy: function () {
                var that = this;
                clearTimeout(that._busy);
                that._loading.hide();
                that.element.attr("aria-busy", false);
                that._busy = null;
            },

            _showBusy: function () {
                var that = this;

                if (that._busy) {
                    return;
                }

                that._busy = setTimeout(function () {
                    that.element.attr("aria-busy", true);
                    that._loading.show();
                }, 100);
            },

            _placeholder: function (show) {
                if (placeholderSupported) {
                    return;
                }

                var that = this,
                element = that.element,
                placeholder = that.options.placeholder,
                value;

                if (placeholder) {
                    value = element.val();

                    if (show === undefined) {
                        show = !value;
                    }

                    if (!show) {
                        if (value !== placeholder) {
                            placeholder = value;
                        } else {
                            placeholder = "";
                        }
                    }

                    if (value === that._old && !show) {
                        return;
                    }

                    element.toggleClass("k-readonly", show)
                       .val(placeholder);

                    if (!placeholder && element[0] === document.activeElement) {
                        caret(element[0], 0, 0);
                    }
                }
            },

            _search: function () {
                var that = this;
                clearTimeout(that._typing);

                that._typing = setTimeout(function () {
                    if (that._prev !== that._accessor()) {
                        that._prev = that._accessor();
                        that.search();
                    }
                }, that.options.delay);
            },

            _select: function (li) {
                var that = this,
                separator = that.options.separator,
                data = that._data(),
                text,
                idx;

                li = $(li);

                if (li[0] && !li.hasClass(SELECTED)) {
                    idx = List.inArray(li[0], that.ul[0]);

                    if (idx > -1) {
                        data = data[idx];
                        text = that._text(data);

                        if (separator) {
                            text = replaceWordAtCaret(caret(that.element)[0], that._accessor(), text, separator);
                        }

                        that._accessor(text);
                        that._prev = that._accessor();
                        that.current(li.addClass(SELECTED));
                    }
                }
            },

            _loader: function () {
                this._loading = $('<span class="k-icon k-loading" style="display:none"></span>').insertAfter(this.element);
            },

            _toggleHover: function (e) {
                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
            },

            _wrapper: function () {
                var that = this,
                element = that.element,
                DOMelement = element[0],
                wrapper;

                wrapper = element.parent();

                if (!wrapper.is("span.k-widget")) {
                    wrapper = element.wrap("<span />").parent();
                }

                //aria

                wrapper.attr("tabindex", -1);
                wrapper.attr("role", "presentation");

                //end

                wrapper[0].style.cssText = DOMelement.style.cssText;
                element.css({
                    width: "100%",
                    height: DOMelement.style.height
                });

                that._focused = that.element;
                that.wrapper = wrapper
                              .addClass("k-widget k-autocomplete k-header")
                              .addClass(DOMelement.className);
            }
        });

        ui.plugin(AutoComplete);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        Select = ui.Select,
        os = kendo.support.mobileOS,
        activeElement = kendo._activeElement,
        keys = kendo.keys,
        ns = ".kendoDropDownList",
        DISABLED = "disabled",
        READONLY = "readonly",
        CHANGE = "change",
        FOCUSED = "k-state-focused",
        DEFAULT = "k-state-default",
        STATEDISABLED = "k-state-disabled",
        ARIA_DISABLED = "aria-disabled",
        ARIA_READONLY = "aria-readonly",
        SELECTED = "k-state-selected",
        HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns,
        TABINDEX = "tabindex",
        STATE_FILTER = "filter",
        STATE_ACCEPT = "accept",
        proxy = $.proxy;

        var DropDownList = Select.extend({
            init: function (element, options) {
                var that = this,
                index = options && options.index,
                optionLabel, useOptionLabel, text;

                that.ns = ns;
                options = $.isArray(options) ? { dataSource: options} : options;

                Select.fn.init.call(that, element, options);

                options = that.options;
                element = that.element.on("focus" + ns, proxy(that._focusHandler, that));

                that._inputTemplate();

                that._reset();

                that._prev = "";
                that._word = "";

                that._wrapper();

                that._tabindex();
                that.wrapper.data(TABINDEX, that.wrapper.attr(TABINDEX));

                that._span();

                that._popup();

                that._mobile();

                that._dataSource();
                that._ignoreCase();

                that._filterHeader();

                that._aria();

                that._enable();

                that._oldIndex = that.selectedIndex = -1;

                that._cascade();

                if (index !== undefined) {
                    options.index = index;
                }

                if (options.autoBind) {
                    that.dataSource.fetch();
                } else if (that.selectedIndex === -1) {
                    text = options.text || "";
                    if (!text) {
                        optionLabel = options.optionLabel;
                        useOptionLabel = optionLabel && options.index === 0;

                        if (that._isSelect) {
                            if (useOptionLabel) {
                                text = optionLabel;
                            } else {
                                text = element.children(":selected").text();
                            }
                        } else if (!element[0].value && useOptionLabel) {
                            text = optionLabel;
                        }
                    }

                    that._textAccessor(text);
                }

                kendo.notify(that);
            },

            options: {
                name: "DropDownList",
                enabled: true,
                autoBind: true,
                index: 0,
                text: null,
                value: null,
                template: "",
                valueTemplate: "",
                delay: 500,
                height: 200,
                dataTextField: "",
                dataValueField: "",
                optionLabel: "",
                cascadeFrom: "",
                cascadeFromField: "",
                ignoreCase: true,
                animation: {},
                filter: "none",
                minLength: 1
            },
            events: [
            "open",
            "close",
            CHANGE,
            "select",
            "filtering",
            "dataBinding",
            "dataBound",
            "cascade"
        ],

            setOptions: function (options) {
                Select.fn.setOptions.call(this, options);

                this._template();
                this._inputTemplate();
                this._accessors();
                this._filterHeader();
                this._enable();
                this._aria();
            },

            destroy: function () {
                var that = this;

                that.wrapper.off(ns);
                that.element.off(ns);
                that._inputWrapper.off(ns);

                that._arrow.off();
                that._arrow = null;

                Select.fn.destroy.call(that);
            },

            open: function () {
                var that = this;

                if (that.popup.visible()) {
                    return;
                }

                if (!that.ul[0].firstChild || that._state === STATE_ACCEPT) {
                    that._open = true;
                    that._state = "rebind";

                    if (that.filterInput) {
                        that.filterInput.val("");
                    }

                    that._filterSource();
                } else {
                    that.popup.open();
                    that._focusElement(that.filterInput);
                    that._scroll(that._current);
                }
            },

            toggle: function (toggle) {
                this._toggle(toggle, true);
            },

            refresh: function () {
                var that = this,
                data = that._data(),
                length = data.length,
                optionLabel = that.options.optionLabel,
                filtered = that._state === STATE_FILTER,
                element = that.element[0],
                selectedIndex,
                value;


                that.trigger("dataBinding");
                if (that._current) {
                    that.current(null);
                }

                that._angularItems("cleanup");
                that.ul[0].innerHTML = kendo.render(that.template, data);
                that._angularItems("compile");

                that._height(filtered ? (length || 1) : length);

                if (that.popup.visible()) {
                    that.popup._position();
                }

                if (that._isSelect) {
                    selectedIndex = element.selectedIndex;
                    value = that.value();

                    if (length) {
                        if (optionLabel) {
                            optionLabel = that._option("", that._optionLabelText(optionLabel));
                        }
                    } else if (value) {
                        selectedIndex = 0;
                        optionLabel = that._option(value, that.text());
                    }

                    that._options(data, optionLabel);
                    element.selectedIndex = selectedIndex === -1 ? 0 : selectedIndex;
                }

                that._hideBusy();
                that._makeUnselectable();

                if (!filtered) {
                    if (that._open) {
                        that.toggle(!!length);
                    }

                    that._open = false;

                    if (!that._fetch) {
                        if (length) {
                            that._selectItem();
                        } else if (that._textAccessor() !== optionLabel) {
                            that.element.val("");
                            that._textAccessor("");
                        }
                    }
                } else {
                    that.current($(that.ul[0].firstChild));
                }

                that._bound = !!length;
                that.trigger("dataBound");
            },

            text: function (text) {
                var that = this;
                var dataItem, loweredText;
                var ignoreCase = that.options.ignoreCase;

                text = text === null ? "" : text;

                if (text !== undefined) {
                    if (typeof text === "string") {
                        loweredText = ignoreCase ? text.toLowerCase() : text;

                        dataItem = that._select(function (data) {
                            data = that._text(data);

                            if (ignoreCase) {
                                data = (data + "").toLowerCase();
                            }

                            return data === loweredText;
                        });

                        if (dataItem) {
                            text = dataItem;
                        }
                    }

                    that._textAccessor(text);
                } else {
                    return that._textAccessor();
                }
            },

            value: function (value) {
                var that = this,
                idx, hasValue;

                if (value !== undefined) {
                    if (value !== null) {
                        value = value.toString();
                    }

                    that._selectedValue = value;

                    hasValue = value || (that.options.optionLabel && !that.element[0].disabled && value === "");
                    if (hasValue && that._fetchItems(value)) {
                        return;
                    }

                    idx = that._index(value);
                    that.select(idx > -1 ? idx : 0);
                } else {
                    return that._accessor();
                }
            },

            _focusHandler: function () {
                this.wrapper.focus();
            },

            _focusinHandler: function () {
                this._inputWrapper.addClass(FOCUSED);
                this._prevent = false;
            },

            _focusoutHandler: function () {
                var that = this;
                var filtered = that._state === STATE_FILTER;
                var isIFrame = window.self !== window.top;

                if (!that._prevent) {
                    if (filtered) {
                        that._select(that._current);
                    }

                    if (!filtered || that.dataItem()) {
                        that._triggerCascade();
                    }

                    if (kendo.support.mobileOS.ios && isIFrame) {
                        that._change();
                    } else {
                        that._blur();
                    }

                    that._inputWrapper.removeClass(FOCUSED);
                    that._prevent = true;
                    that._open = false;
                    that.element.blur();
                }
            },

            _wrapperMousedown: function () {
                this._prevent = !!this.filterInput;
            },

            _wrapperClick: function (e) {
                e.preventDefault();
                this._focused = this.wrapper;
                this._toggle();
            },

            _editable: function (options) {
                var that = this,
                element = that.element,
                disable = options.disable,
                readonly = options.readonly,
                wrapper = that.wrapper.add(that.filterInput).off(ns),
                dropDownWrapper = that._inputWrapper.off(HOVEREVENTS);

                if (!readonly && !disable) {
                    element.removeAttr(DISABLED).removeAttr(READONLY);

                    dropDownWrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED)
                    .on(HOVEREVENTS, that._toggleHover);

                    wrapper
                    .attr(TABINDEX, wrapper.data(TABINDEX))
                    .attr(ARIA_DISABLED, false)
                    .attr(ARIA_READONLY, false)
                    .on("keydown" + ns, proxy(that._keydown, that))
                    .on("focusin" + ns, proxy(that._focusinHandler, that))
                    .on("focusout" + ns, proxy(that._focusoutHandler, that))
                    .on("mousedown" + ns, proxy(that._wrapperMousedown, that));

                    that.wrapper.on("click" + ns, proxy(that._wrapperClick, that));

                    if (!that.filterInput) {
                        wrapper.on("keypress" + ns, proxy(that._keypress, that));
                    }

                } else if (disable) {
                    wrapper.removeAttr(TABINDEX);
                    dropDownWrapper
                    .addClass(STATEDISABLED)
                    .removeClass(DEFAULT);
                } else {
                    dropDownWrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED);

                    wrapper
                    .on("focusin" + ns, proxy(that._focusinHandler, that))
                    .on("focusout" + ns, proxy(that._focusoutHandler, that));
                }

                element.attr(DISABLED, disable)
                   .attr(READONLY, readonly);

                wrapper.attr(ARIA_DISABLED, disable)
                   .attr(ARIA_READONLY, readonly);
            },

            _accept: function (li, key) {
                var that = this;
                var activeFilter = that.filterInput && that.filterInput[0] === activeElement();

                that._focus(li);
                that._focusElement(that.wrapper);

                if (activeFilter && key === keys.TAB) {
                    that.wrapper.focusout();
                }
            },

            _option: function (value, text) {
                return '<option value="' + value + '">' + text + "</option>";
            },

            _optionLabelText: function () {
                var options = this.options,
                dataTextField = options.dataTextField,
                optionLabel = options.optionLabel;

                if (optionLabel && dataTextField && typeof optionLabel === "object") {
                    return this._text(optionLabel);
                }

                return optionLabel;
            },

            _data: function () {
                var that = this,
                options = that.options,
                optionLabel = options.optionLabel,
                textField = options.dataTextField,
                valueField = options.dataValueField,
                data = that.dataSource.view(),
                length = data.length,
                first = optionLabel,
                idx = 0;

                if (optionLabel && length) {
                    if (typeof optionLabel === "object") {
                        first = optionLabel;
                    } else if (textField) {
                        first = {};

                        textField = textField.split(".");
                        valueField = valueField.split(".");

                        assign(first, valueField, "");
                        assign(first, textField, optionLabel);
                    }

                    first = new kendo.data.ObservableArray([first]);

                    for (; idx < length; idx++) {
                        first.push(data[idx]);
                    }
                    data = first;
                }

                return data;
            },

            _selectItem: function () {
                Select.fn._selectItem.call(this);

                if (!this.current()) {
                    this.select(0);
                }
            },

            _keydown: function (e) {
                var that = this;
                var key = e.keyCode;
                var altKey = e.altKey;
                var ul = that.ul[0];
                var handled;

                if (key === keys.LEFT) {
                    key = keys.UP;
                } else if (key === keys.RIGHT) {
                    key = keys.DOWN;
                }

                e.keyCode = key;

                handled = that._move(e);

                if (!that.popup.visible() || !that.filterInput) {
                    if (key === keys.HOME) {
                        handled = true;
                        e.preventDefault();
                        that._select(ul.firstChild);
                    } else if (key === keys.END) {
                        handled = true;
                        e.preventDefault();
                        that._select(ul.lastChild);
                    }
                }

                if (altKey && key === keys.UP) {
                    that._focusElement(that.wrapper);
                }

                if (!altKey && !handled && that.filterInput) {
                    that._search();
                }
            },

            _selectNext: function (word, index) {
                var that = this, text,
                startIndex = index,
                data = that._data(),
                length = data.length,
                ignoreCase = that.options.ignoreCase,
                action = function (text, index) {
                    text = text + "";
                    if (ignoreCase) {
                        text = text.toLowerCase();
                    }

                    if (text.indexOf(word) === 0) {
                        that._select(index);
                        that._triggerEvents();
                        return true;
                    }
                };

                for (; index < length; index++) {
                    text = that._text(data[index]);
                    if (text && action(text, index)) {
                        return true;
                    }
                }

                if (startIndex > 0 && startIndex < length) {
                    index = 0;
                    for (; index <= startIndex; index++) {
                        text = that._text(data[index]);
                        if (text && action(text, index)) {
                            return true;
                        }
                    }
                }

                return false;
            },

            _keypress: function (e) {
                var that = this;

                if (e.which === 0 || e.keyCode === kendo.keys.ENTER) {
                    return;
                }

                var character = String.fromCharCode(e.charCode || e.keyCode);
                var index = that.selectedIndex;
                var word = that._word;

                if (that.options.ignoreCase) {
                    character = character.toLowerCase();
                }

                if (character === " ") {
                    e.preventDefault();
                }

                if (that._last === character && word.length <= 1 && index > -1) {
                    if (!word) {
                        word = character;
                    }

                    if (that._selectNext(word, index + 1)) {
                        return;
                    }
                }

                that._word = word + character;
                that._last = character;

                that._search();
            },

            _popupOpen: function () {
                var popup = this.popup;

                popup.wrapper = kendo.wrap(popup.element);

                if (popup.element.closest(".km-root")[0]) {
                    popup.wrapper.addClass("km-popup km-widget");
                    this.wrapper.addClass("km-widget");
                }
            },

            _popup: function () {
                Select.fn._popup.call(this);
                this.popup.one("open", proxy(this._popupOpen, this));
            },

            _focusElement: function (element) {
                var active = activeElement();
                var wrapper = this.wrapper;
                var filterInput = this.filterInput;
                var compareElement = element === filterInput ? wrapper : filterInput;

                if (filterInput && compareElement[0] === active) {
                    this._prevent = true;
                    this._focused = element.focus();
                }
            },

            _filter: function (word) {
                if (word) {
                    var that = this;
                    var ignoreCase = that.options.ignoreCase;

                    if (ignoreCase) {
                        word = word.toLowerCase();
                    }

                    that._select(function (dataItem) {
                        var text = that._text(dataItem);

                        if (text !== undefined) {
                            text = (text + "");
                            if (ignoreCase) {
                                text = text.toLowerCase();
                            }

                            return text.indexOf(word) === 0;
                        }
                    });
                }
            },

            _search: function () {
                var that = this,
                dataSource = that.dataSource,
                index = that.selectedIndex,
                word = that._word;

                clearTimeout(that._typing);

                if (that.options.filter !== "none") {
                    that._typing = setTimeout(function () {
                        var value = that.filterInput.val();

                        if (that._prev !== value) {
                            that._prev = value;
                            that.search(value);
                        }

                        that._typing = null;
                    }, that.options.delay);
                } else {
                    that._typing = setTimeout(function () {
                        that._word = "";
                    }, that.options.delay);

                    if (index === -1) {
                        index = 0;
                    }

                    if (!that.ul[0].firstChild) {
                        dataSource.one(CHANGE, function () {
                            if (dataSource.data()[0] && index > -1) {
                                that._selectNext(word, index);
                            }
                        }).fetch();
                        return;
                    }

                    that._selectNext(word, index);
                    that._triggerEvents();
                }
            },

            _select: function (li) {
                var that = this,
                current = that._current,
                data = null,
                value,
                idx;

                li = that._get(li);

                if (li && li[0] && !li.hasClass(SELECTED)) {
                    if (that._state === STATE_FILTER) {
                        that._state = STATE_ACCEPT;
                    }

                    if (current) {
                        current.removeClass(SELECTED);
                    }

                    idx = ui.List.inArray(li[0], that.ul[0]);
                    if (idx > -1) {
                        that.selectedIndex = idx;

                        data = that._data()[idx];
                        value = that._value(data);

                        if (value === null) {
                            value = "";
                        }

                        that._textAccessor(data);
                        that._accessor(value !== undefined ? value : that._text(data), idx);
                        that._selectedValue = that._accessor();

                        that.current(li.addClass(SELECTED));

                        if (that._optionID) {
                            that._current.attr("aria-selected", true);
                        }
                    }
                }

                return data;
            },

            _triggerEvents: function () {
                if (!this.popup.visible()) {
                    this._triggerCascade();
                    this._change();
                }
            },

            _mobile: function () {
                var that = this,
                popup = that.popup,
                root = popup.element.parents(".km-root").eq(0);

                if (root.length && os) {
                    popup.options.animation.open.effects = (os.android || os.meego) ? "fadeIn" : (os.ios || os.wp) ? "slideIn:up" : popup.options.animation.open.effects;
                }
            },

            _filterHeader: function () {
                var icon;
                var options = this.options;
                var filterEnalbed = options.filter !== "none";

                if (this.filterInput) {
                    this.filterInput
                    .off(ns)
                    .parent()
                    .remove();

                    this.filterInput = null;
                }

                if (filterEnalbed) {
                    icon = '<span unselectable="on" class="k-icon k-i-search">select</span>';

                    this.filterInput = $('<input class="k-textbox"/>')
                                      .attr({
                                          role: "listbox",
                                          "aria-haspopup": true,
                                          "aria-expanded": false
                                      });

                    this.list
                    .prepend($('<span class="k-list-filter" />')
                    .append(this.filterInput.add(icon)));
                }
            },

            _span: function () {
                var that = this,
                wrapper = that.wrapper,
                SELECTOR = "span.k-input",
                span;

                span = wrapper.find(SELECTOR);

                if (!span[0]) {
                    wrapper.append('<span unselectable="on" class="k-dropdown-wrap k-state-default"><span unselectable="on" class="k-input">&nbsp;</span><span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-arrow-s">select</span></span></span>')
                       .append(that.element);

                    span = wrapper.find(SELECTOR);
                }

                that.span = span;
                that._inputWrapper = $(wrapper[0].firstChild);
                that._arrow = wrapper.find(".k-icon");
            },

            _wrapper: function () {
                var that = this,
                element = that.element,
                DOMelement = element[0],
                wrapper;

                wrapper = element.parent();

                if (!wrapper.is("span.k-widget")) {
                    wrapper = element.wrap("<span />").parent();
                    wrapper[0].style.cssText = DOMelement.style.cssText;
                }

                element.hide();

                that._focused = that.wrapper = wrapper
                              .addClass("k-widget k-dropdown k-header")
                              .addClass(DOMelement.className)
                              .css("display", "")
                              .attr({
                                  unselectable: "on",
                                  role: "listbox",
                                  "aria-haspopup": true,
                                  "aria-expanded": false
                              });
            },

            _clearSelection: function () {
                var that = this;
                var optionLabel = that.options.optionLabel;

                that.options.value = "";
                that._selectedValue = "";

                if (that.dataSource.view()[0] && (optionLabel || that._userTriggered)) {
                    that.select(0);
                    return;
                }

                that.selectedIndex = -1;

                that.element.val("");
                that._textAccessor(that.options.optionLabel);
            },

            _inputTemplate: function () {
                var that = this,
                template = that.options.valueTemplate;


                if (!template) {
                    template = $.proxy(kendo.template('#:this._text(data)#', { useWithBlock: false }), that);
                } else {
                    template = kendo.template(template);
                }

                that.valueTemplate = template;
            },

            _textAccessor: function (text) {
                var dataItem = this.dataItem();
                var options = this.options;
                var span = this.span;

                if (text !== undefined) {
                    if ($.isPlainObject(text) || text instanceof kendo.data.ObservableObject) {
                        dataItem = text;
                    } else if (!dataItem || this._text(dataItem) !== text) {
                        if (options.dataTextField) {
                            dataItem = {};
                            assign(dataItem, options.dataTextField.split("."), text);
                            assign(dataItem, options.dataValueField.split("."), this._accessor());
                        } else {
                            dataItem = text;
                        }
                    }

                    var getElements = function () {
                        return {
                            elements: span.get(),
                            data: [{ dataItem: dataItem}]
                        };
                    };
                    this.angular("cleanup", getElements);
                    span.html(this.valueTemplate(dataItem));
                    this.angular("compile", getElements);
                } else {
                    return span.text();
                }
            }
        });

        function assign(instance, fields, value) {
            var idx = 0,
            lastIndex = fields.length - 1,
            field;

            for (; idx < lastIndex; ++idx) {
                field = fields[idx];

                if (!(field in instance)) {
                    instance[field] = {};
                }

                instance = instance[field];
            }

            instance[fields[lastIndex]] = value;
        }

        ui.plugin(DropDownList);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        List = ui.List,
        Select = ui.Select,
        caret = kendo.caret,
        support = kendo.support,
        placeholderSupported = support.placeholder,
        activeElement = kendo._activeElement,
        keys = kendo.keys,
        ns = ".kendoComboBox",
        CLICK = "click" + ns,
        MOUSEDOWN = "mousedown" + ns,
        DISABLED = "disabled",
        READONLY = "readonly",
        CHANGE = "change",
        DEFAULT = "k-state-default",
        FOCUSED = "k-state-focused",
        STATEDISABLED = "k-state-disabled",
        ARIA_DISABLED = "aria-disabled",
        ARIA_READONLY = "aria-readonly",
        STATE_SELECTED = "k-state-selected",
        STATE_FILTER = "filter",
        STATE_ACCEPT = "accept",
        STATE_REBIND = "rebind",
        HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns,
        NULL = null,
        proxy = $.proxy;

        var ComboBox = Select.extend({
            init: function (element, options) {
                var that = this, text;

                that.ns = ns;

                options = $.isArray(options) ? { dataSource: options} : options;

                Select.fn.init.call(that, element, options);

                options = that.options;
                element = that.element.on("focus" + ns, proxy(that._focusHandler, that));

                options.placeholder = options.placeholder || element.attr("placeholder");

                that._reset();

                that._wrapper();

                that._input();

                that._tabindex(that.input);

                that._popup();

                that._dataSource();
                that._ignoreCase();

                that._enable();

                that._oldIndex = that.selectedIndex = -1;

                that._cascade();

                that._aria();

                if (options.autoBind) {
                    that._filterSource(); //TODO: diff when just bind and actually filter
                } else {
                    text = options.text;

                    if (!text && that._isSelect) {
                        text = element.children(":selected").text();
                    }

                    if (text) {
                        that.input.val(text);
                        that._prev = text;
                    }
                }

                if (!text) {
                    that._placeholder();
                }

                kendo.notify(that);
            },

            options: {
                name: "ComboBox",
                enabled: true,
                index: -1,
                text: null,
                value: null,
                autoBind: true,
                delay: 200,
                dataTextField: "",
                dataValueField: "",
                minLength: 0,
                height: 200,
                highlightFirst: true,
                template: "",
                filter: "none",
                placeholder: "",
                suggest: false,
                cascadeFrom: "",
                cascadeFromField: "",
                ignoreCase: true,
                animation: {}
            },

            events: [
            "open",
            "close",
            CHANGE,
            "select",
            "filtering",
            "dataBinding",
            "dataBound",
            "cascade"
        ],

            setOptions: function (options) {
                Select.fn.setOptions.call(this, options);

                this._template();
                this._accessors();
                this._aria();
            },

            current: function (li) {
                var that = this,
                current = that._current;

                if (li === undefined) {
                    return current;
                }

                if (current) {
                    current.removeClass(STATE_SELECTED);
                }

                Select.fn.current.call(that, li);
            },

            destroy: function () {
                var that = this;

                that.input.off(ns);
                that.element.off(ns);
                that._inputWrapper.off(ns);

                Select.fn.destroy.call(that);
            },

            _focusHandler: function () {
                this.input.focus();
            },

            _arrowClick: function () {
                this._toggle();
            },

            _inputFocus: function () {
                this._inputWrapper.addClass(FOCUSED);
                this._placeholder(false);
            },

            _inputFocusout: function () {
                var that = this;

                that._inputWrapper.removeClass(FOCUSED);
                clearTimeout(that._typing);
                that._typing = null;

                if (that.options.text !== that.input.val()) {
                    that.text(that.text());
                }

                that._placeholder();
                that._blur();

                that.element.blur();
            },

            _editable: function (options) {
                var that = this,
                disable = options.disable,
                readonly = options.readonly,
                wrapper = that._inputWrapper.off(ns),
                input = that.element.add(that.input.off(ns)),
                arrow = that._arrow.parent().off(CLICK + " " + MOUSEDOWN);

                if (!readonly && !disable) {
                    wrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED)
                    .on(HOVEREVENTS, that._toggleHover);

                    input.removeAttr(DISABLED)
                     .removeAttr(READONLY)
                     .attr(ARIA_DISABLED, false)
                     .attr(ARIA_READONLY, false);

                    arrow.on(CLICK, proxy(that._arrowClick, that))
                     .on(MOUSEDOWN, function (e) { e.preventDefault(); });

                    that.input
                    .on("keydown" + ns, proxy(that._keydown, that))
                    .on("focus" + ns, proxy(that._inputFocus, that))
                    .on("focusout" + ns, proxy(that._inputFocusout, that));

                } else {
                    wrapper
                    .addClass(disable ? STATEDISABLED : DEFAULT)
                    .removeClass(disable ? DEFAULT : STATEDISABLED);

                    input.attr(DISABLED, disable)
                     .attr(READONLY, readonly)
                     .attr(ARIA_DISABLED, disable)
                     .attr(ARIA_READONLY, readonly);
                }
            },

            open: function () {
                var that = this;
                var state = that._state;
                var serverFiltering = that.dataSource.options.serverFiltering;

                if (that.popup.visible()) {
                    return;
                }

                if ((!that.ul[0].firstChild && state !== STATE_FILTER) ||
                (state === STATE_ACCEPT && !serverFiltering)) {
                    that._open = true;
                    that._state = STATE_REBIND;
                    that._filterSource();
                } else {
                    that.popup.open();
                    that._scroll(that._current);
                }
            },

            refresh: function () {
                var that = this,
                ul = that.ul[0],
                options = that.options,
                state = that._state,
                data = that._data(),
                length = data.length,
                keepState = true,
                hasChild, custom;

                that._angularItems("cleanup");
                that.trigger("dataBinding");

                ul.innerHTML = kendo.render(that.template, data);
                that._height(length);

                if (that.popup.visible()) {
                    that.popup._position();
                }

                if (that._isSelect) {
                    hasChild = that.element[0].children[0];

                    if (state === STATE_REBIND) {
                        that._state = "";
                    }

                    custom = that._option;
                    that._option = undefined;
                    that._options(data);

                    if (custom && custom[0].selected) {
                        that._custom(custom.val(), keepState);
                    } else if (!that._bound && !hasChild) {
                        that._custom("", keepState);
                    }
                }

                if (length) {
                    if (options.highlightFirst) {
                        that.current($(ul.firstChild));
                    }

                    if (options.suggest && that.input.val() && that._request !== undefined /*first refresh ever*/) {
                        that.suggest($(ul.firstChild));
                    }
                }

                if (state !== STATE_FILTER && !that._fetch) {
                    that._selectItem();
                }

                if (that._open) {
                    that._open = false;

                    if (that._typing && that.input[0] !== activeElement()) {
                        that.popup.close();
                    } else {
                        that.toggle(!!length);
                    }

                    that._typing = null;
                }

                if (that._touchScroller) {
                    that._touchScroller.reset();
                }

                that._makeUnselectable();

                that._hideBusy();
                that._bound = true;
                that._angularItems("compile");
                that.trigger("dataBound");
            },

            suggest: function (word) {
                var that = this,
                element = that.input[0],
                value = that.text(),
                caretIdx = caret(element)[0],
                key = that._last,
                idx;

                if (key == keys.BACKSPACE || key == keys.DELETE) {
                    that._last = undefined;
                    return;
                }

                word = word || "";

                if (typeof word !== "string") {
                    idx = List.inArray(word[0], that.ul[0]);

                    if (idx > -1) {
                        word = that._text(that.dataSource.view()[idx]);
                    } else {
                        word = "";
                    }
                }

                if (caretIdx <= 0) {
                    caretIdx = value.toLowerCase().indexOf(word.toLowerCase()) + 1;
                }

                if (word) {
                    idx = word.toLowerCase().indexOf(value.toLowerCase());
                    if (idx > -1) {
                        value += word.substring(idx + value.length);
                    }
                } else {
                    value = value.substring(0, caretIdx);
                }

                if (value.length !== caretIdx || !word) {
                    element.value = value;
                    if (element === activeElement()) {
                        caret(element, caretIdx, value.length);
                    }
                }
            },

            text: function (text) {
                text = text === null ? "" : text;

                var that = this;
                var input = that.input[0];
                var ignoreCase = that.options.ignoreCase;
                var loweredText = text;
                var dataItem;
                var value;

                if (text !== undefined) {
                    dataItem = that.dataItem();

                    if (dataItem && that._text(dataItem) === text) {
                        value = that._value(dataItem);
                        if (value === null) {
                            value = "";
                        } else {
                            value += "";
                        }

                        if (value === that._old) {
                            that._triggerCascade();
                            return;
                        }
                    }

                    if (ignoreCase) {
                        loweredText = loweredText.toLowerCase();
                    }

                    that._select(function (data) {
                        data = that._text(data);

                        if (ignoreCase) {
                            data = (data + "").toLowerCase();
                        }

                        return data === loweredText;
                    });

                    if (that.selectedIndex < 0) {
                        that._custom(text);
                        input.value = text;
                    }

                    that._prev = input.value;
                    that._triggerCascade();
                } else {
                    return input.value;
                }
            },

            toggle: function (toggle) {
                this._toggle(toggle, true);
            },

            value: function (value) {
                var that = this,
                options = that.options,
                idx;

                if (value !== undefined) {
                    if (value !== null) {
                        value = value.toString();
                    }

                    that._selectedValue = value;

                    if (!that._open && value && that._fetchItems(value)) {
                        return;
                    }

                    idx = that._index(value);

                    if (idx > -1) {
                        that.select(idx);
                    } else {
                        that.current(NULL);
                        that._custom(value);

                        if (options.value !== value || options.text !== that.input.val()) {
                            that.text(value);
                            that._placeholder();
                        }
                    }

                    that._old = that._accessor();
                    that._oldIndex = that.selectedIndex;
                } else {
                    return that._accessor();
                }
            },

            _accept: function (li) {
                var that = this;

                if (li) {
                    that._focus(li);
                } else {
                    that.text(that.text());
                    that._change();
                }
            },

            _custom: function (value, keepState) {
                var that = this;
                var element = that.element;
                var custom = that._option;

                if (that._state === STATE_FILTER && !keepState) {
                    that._state = STATE_ACCEPT;
                }

                if (that._isSelect) {
                    if (!custom) {
                        custom = that._option = $("<option/>");
                        element.append(custom);
                    }
                    custom.text(value);
                    custom[0].selected = true;
                } else {
                    element.val(value);
                }

                that._selectedValue = value;
            },

            _filter: function (word) {
                var that = this,
                options = that.options,
                dataSource = that.dataSource,
                ignoreCase = options.ignoreCase,
                predicate = function (dataItem) {
                    var text = that._text(dataItem);
                    if (text !== undefined) {
                        text = text + "";
                        if (text !== "" && word === "") {
                            return false;
                        }

                        if (ignoreCase) {
                            text = text.toLowerCase();
                        }

                        return text.indexOf(word) === 0;
                    }
                };

                if (ignoreCase) {
                    word = word.toLowerCase();
                }

                if (!that.ul[0].firstChild) {
                    dataSource.one(CHANGE, function () {
                        if (dataSource.view()[0]) {
                            that.search(word);
                        }
                    }).fetch();
                    return;
                }

                if (that._highlight(predicate) !== -1) {
                    if (options.suggest && that._current) {
                        that.suggest(that._current);
                    }
                    that.open();
                }

                that._hideBusy();
            },

            _highlight: function (li) {
                var that = this, idx;

                if (li === undefined || li === null) {
                    return -1;
                }

                li = that._get(li);
                idx = List.inArray(li[0], that.ul[0]);

                if (idx == -1) {
                    if (that.options.highlightFirst && !that.text()) {
                        li = that.ul[0].firstChild;
                        if (li) {
                            li = $(li);
                        }
                    } else {
                        li = NULL;
                    }
                }

                that.current(li);

                return idx;
            },

            _input: function () {
                var that = this,
                element = that.element.removeClass("k-input")[0],
                accessKey = element.accessKey,
                wrapper = that.wrapper,
                SELECTOR = "input.k-input",
                name = element.name || "",
                input;

                if (name) {
                    name = 'name="' + name + '_input" ';
                }

                input = wrapper.find(SELECTOR);

                if (!input[0]) {
                    wrapper.append('<span tabindex="-1" unselectable="on" class="k-dropdown-wrap k-state-default"><input ' + name + 'class="k-input" type="text" autocomplete="off"/><span tabindex="-1" unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-arrow-s">select</span></span></span>')
                       .append(that.element);

                    input = wrapper.find(SELECTOR);
                }

                input[0].style.cssText = element.style.cssText;

                if (element.maxLength > -1) {
                    input[0].maxLength = element.maxLength;
                }

                input.addClass(element.className)
                 .val(this.options.text || element.value)
                 .css({
                     width: "100%",
                     height: element.style.height
                 })
                 .attr({
                     "role": "combobox",
                     "aria-expanded": false
                 })
                 .show();

                if (placeholderSupported) {
                    input.attr("placeholder", that.options.placeholder);
                }

                if (accessKey) {
                    element.accessKey = "";
                    input[0].accessKey = accessKey;
                }

                that._focused = that.input = input;
                that._inputWrapper = $(wrapper[0].firstChild);
                that._arrow = wrapper.find(".k-icon")
                                 .attr({
                                     "role": "button",
                                     "tabIndex": -1
                                 });

                if (element.id) {
                    that._arrow.attr("aria-controls", that.ul[0].id);
                }
            },

            _keydown: function (e) {
                var that = this,
                key = e.keyCode;

                that._last = key;

                clearTimeout(that._typing);
                that._typing = null;

                if (key != keys.TAB && !that._move(e)) {
                    that._search();
                }
            },

            _placeholder: function (show) {
                if (placeholderSupported) {
                    return;
                }

                var that = this,
                input = that.input,
                placeholder = that.options.placeholder,
                value;

                if (placeholder) {
                    value = that.value();

                    if (show === undefined) {
                        show = !value;
                    }

                    input.toggleClass("k-readonly", show);

                    if (!show) {
                        if (!value) {
                            placeholder = "";
                        } else {
                            return;
                        }
                    }

                    input.val(placeholder);

                    if (!placeholder && input[0] === activeElement()) {
                        caret(input[0], 0, 0);
                    }
                }
            },

            _search: function () {
                var that = this;

                that._typing = setTimeout(function () {
                    var value = that.text();

                    if (that._prev !== value) {
                        that._prev = value;
                        that.search(value);
                    }

                    that._typing = null;
                }, that.options.delay);
            },

            _select: function (li) {
                var that = this,
                text,
                value,
                data = that._data(),
                idx = that._highlight(li);

                that.selectedIndex = idx;

                if (idx !== -1) {
                    if (that._state === STATE_FILTER) {
                        that._state = STATE_ACCEPT;
                    }

                    that._current.addClass(STATE_SELECTED);

                    data = data[idx];
                    text = that._text(data);
                    value = that._value(data);

                    if (value === null) {
                        value = "";
                    }

                    that._prev = that.input[0].value = text;
                    that._accessor(value !== undefined ? value : text, idx);
                    that._selectedValue = that._accessor();
                    that._placeholder();

                    if (that._optionID) {
                        that._current.attr("aria-selected", true);
                    }
                }
            },

            _wrapper: function () {
                var that = this,
                element = that.element,
                wrapper = element.parent();

                if (!wrapper.is("span.k-widget")) {
                    wrapper = element.hide().wrap("<span />").parent();
                    wrapper[0].style.cssText = element[0].style.cssText;
                }

                that.wrapper = wrapper.addClass("k-widget k-combobox k-header")
                                  .addClass(element[0].className)
                                  .css("display", "");
            },

            _clearSelection: function (parent, isFiltered) {
                var that = this,
                hasValue = parent._selectedValue || parent.value(),
                custom = hasValue && parent.selectedIndex === -1;

                if (isFiltered || !hasValue || custom) {
                    that.value("");
                    that.options.value = "";
                }
            }
        });

        ui.plugin(ComboBox);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        List = ui.List,
        keys = kendo.keys,
        activeElement = kendo._activeElement,
        ObservableArray = kendo.data.ObservableArray,
        proxy = $.proxy,
        ID = "id",
        LI = "li",
        ACCEPT = "accept",
        FILTER = "filter",
        OPEN = "open",
        CLOSE = "close",
        CHANGE = "change",
        PROGRESS = "progress",
        SELECT = "select",
        NEXT = "nextSibling",
        PREV = "previousSibling",
        HIDE = ' style="display:none"',
        ARIA_DISABLED = "aria-disabled",
        ARIA_READONLY = "aria-readonly",
        FOCUSEDCLASS = "k-state-focused",
        HIDDENCLASS = "k-loading-hidden",
        HOVERCLASS = "k-state-hover",
        STATEDISABLED = "k-state-disabled",
        DISABLED = "disabled",
        READONLY = "readonly",
        ns = ".kendoMultiSelect",
        CLICK = "click" + ns,
        KEYDOWN = "keydown" + ns,
        MOUSEENTER = "mouseenter" + ns,
        MOUSELEAVE = "mouseleave" + ns,
        HOVEREVENTS = MOUSEENTER + " " + MOUSELEAVE,
        quotRegExp = /"/g,
        isArray = $.isArray,
        styles = ["font-family",
                  "font-size",
                  "font-stretch",
                  "font-style",
                  "font-weight",
                  "letter-spacing",
                  "text-transform",
                  "line-height"];

        var MultiSelect = List.extend({
            init: function (element, options) {
                var that = this, id, data;

                that.ns = ns;
                List.fn.init.call(that, element, options);

                that._wrapper();
                that._tagList();
                that._input();
                that._textContainer();
                that._loader();

                that._tabindex(that.input);

                element = that.element.attr("multiple", "multiple").hide();
                options = that.options;
                data = options.value;

                if (!options.placeholder) {
                    options.placeholder = element.data("placeholder");
                }

                id = element.attr(ID);

                if (id) {
                    that._tagID = id + "_tag_active";

                    id = id + "_taglist";
                    that.tagList.attr(ID, id);
                }

                that._aria(id);
                that._dataSource();
                that._ignoreCase();
                that._popup();

                that._values = [];
                that._dataItems = [];

                that._reset();
                that._enable();
                that._placeholder();

                if (options.autoBind) {
                    that.dataSource.fetch();
                } else if (data) {
                    if (!isArray(data)) {
                        data = [data];
                    }

                    if ($.isPlainObject(data[0]) || !options.dataValueField) {
                        that._retrieveData = true;
                        that.dataSource.data(data);
                        that.value(that._initialValues);
                    }
                }

                kendo.notify(that);
            },

            options: {
                name: "MultiSelect",
                enabled: true,
                autoBind: true,
                autoClose: true,
                highlightFirst: true,
                dataTextField: "",
                dataValueField: "",
                filter: "startswith",
                ignoreCase: true,
                minLength: 0,
                delay: 100,
                value: null,
                maxSelectedItems: null,
                itemTemplate: "",
                tagTemplate: "",
                placeholder: "",
                height: 200,
                animation: {}
            },

            events: [
            OPEN,
            CLOSE,
            CHANGE,
            SELECT,
            "filtering",
            "dataBinding",
            "dataBound"
        ],

            setDataSource: function (dataSource) {
                this.options.dataSource = dataSource;

                this._dataSource();

                if (this.options.autoBind) {
                    this.dataSource.fetch();
                }
            },

            setOptions: function (options) {
                List.fn.setOptions.call(this, options);

                this._template();
                this._accessors();
                this._aria(this.tagList.attr(ID));
            },

            current: function (candidate) {
                this.currentTag(null);
                return List.fn.current.call(this, candidate);
            },

            currentTag: function (candidate) {
                var that = this;

                if (candidate !== undefined) {
                    if (that._currentTag) {
                        that._currentTag
                        .removeClass(FOCUSEDCLASS)
                        .removeAttr(ID);

                        that.input.removeAttr("aria-activedescendant");
                    }

                    if (candidate) {
                        candidate.addClass(FOCUSEDCLASS).attr(ID, that._tagID);

                        that.input
                        .attr("aria-activedescendant", that._tagID);
                    }

                    that._currentTag = candidate;
                } else {
                    return that._currentTag;
                }
            },

            dataItems: function () {
                return this._dataItems;
            },

            destroy: function () {
                var that = this,
                ns = that.ns;

                clearTimeout(that._busy);
                clearTimeout(that._typing);

                that.wrapper.off(ns);
                that.tagList.off(ns);
                that.input.off(ns);

                List.fn.destroy.call(that);
            },

            _wrapperMousedown: function (e) {
                var that = this;
                var notInput = e.target.nodeName.toLowerCase() !== "input";

                if (notInput) {
                    e.preventDefault();
                }

                if (e.target.className.indexOf("k-delete") === -1) {
                    if (that.input[0] !== activeElement() && notInput) {
                        that.input.focus();
                    }

                    if (that.options.minLength === 0) {
                        that.open();
                    }
                }

            },

            _inputFocus: function () {
                this._placeholder(false);
                this.wrapper.addClass(FOCUSEDCLASS);
            },

            _inputFocusout: function () {
                var that = this;

                clearTimeout(that._typing);

                that.wrapper.removeClass(FOCUSEDCLASS);

                that._placeholder(!that._dataItems[0], true);
                that.close();

                if (that._state === FILTER) {
                    that._state = ACCEPT;
                }

                that.element.blur();
            },

            _tagListClick: function (e) {
                this._unselect($(e.target).closest(LI));
                this._change();
                this.close();
            },

            _editable: function (options) {
                var that = this,
                disable = options.disable,
                readonly = options.readonly,
                wrapper = that.wrapper.off(ns),
                tagList = that.tagList.off(ns),
                input = that.element.add(that.input.off(ns));

                if (!readonly && !disable) {
                    wrapper
                    .removeClass(STATEDISABLED)
                    .on(HOVEREVENTS, that._toggleHover)
                    .on("mousedown" + ns, proxy(that._wrapperMousedown, that));

                    that.input.on(KEYDOWN, proxy(that._keydown, that))
                    .on("paste" + ns, proxy(that._search, that))
                    .on("focus" + ns, proxy(that._inputFocus, that))
                    .on("focusout" + ns, proxy(that._inputFocusout, that));

                    input.removeAttr(DISABLED)
                     .removeAttr(READONLY)
                     .attr(ARIA_DISABLED, false)
                     .attr(ARIA_READONLY, false);

                    tagList
                    .on(MOUSEENTER, LI, function () { $(this).addClass(HOVERCLASS); })
                    .on(MOUSELEAVE, LI, function () { $(this).removeClass(HOVERCLASS); })
                    .on(CLICK, ".k-delete", proxy(that._tagListClick, that));
                } else {
                    if (disable) {
                        wrapper.addClass(STATEDISABLED);
                    } else {
                        wrapper.removeClass(STATEDISABLED);
                    }

                    input.attr(DISABLED, disable)
                     .attr(READONLY, readonly)
                     .attr(ARIA_DISABLED, disable)
                     .attr(ARIA_READONLY, readonly);
                }
            },

            _close: function () {
                var that = this;
                if (that.options.autoClose || !that._visibleItems) {
                    that.close();
                } else {
                    that.current(that.options.highlightFirst ? first(that.ul[0]) : null);
                    that.popup._position();
                }
            },

            close: function () {
                this.popup.close();
                this.current(null);
            },

            open: function () {
                var that = this;

                if (that._request) {
                    that._retrieveData = false;
                }

                if (!that.ul[0].firstChild || that._state === ACCEPT || that._retrieveData) {
                    that._state = "";
                    that._open = true;
                    that._retrieveData = false;
                    that._filterSource();
                } else if (that._visibleItems && that._allowSelection()) {
                    that.popup.open();
                    that.current(that.options.highlightFirst ? first(that.ul[0]) : null);
                }
            },

            toggle: function (toggle) {
                toggle = toggle !== undefined ? toggle : !this.popup.visible();

                this[toggle ? OPEN : CLOSE]();
            },

            refresh: function () {
                var that = this,
                li = null,
                length;

                that.trigger("dataBinding");

                length = that._render(that.dataSource.view());
                that._height(length);

                if (that._setInitialValues) {
                    that._setInitialValues = false;
                    that.value(that._initialValues);
                }

                if (that._open) {
                    that._open = false;
                    that.toggle(length);
                }

                if (that.popup.visible()) {
                    that.popup._position();

                    if (that.options.highlightFirst) {
                        li = first(that.ul[0]);
                    }
                }

                that.current(li);

                if (that._touchScroller) {
                    that._touchScroller.reset();
                }

                that._makeUnselectable();

                that._hideBusy();
                that.trigger("dataBound");
            },

            search: function (word) {
                var that = this,
                options = that.options,
                ignoreCase = options.ignoreCase,
                filter = options.filter,
                field = options.dataTextField,
                inputValue = that.input.val(),
                expression,
                length;

                if (options.placeholder === inputValue) {
                    inputValue = "";
                }

                clearTimeout(that._typing);

                word = typeof word === "string" ? word : inputValue;

                length = word.length;

                if (!length || length >= options.minLength) {
                    that._state = FILTER;
                    that._open = true;

                    expression = {
                        value: ignoreCase ? word.toLowerCase() : word,
                        field: field,
                        operator: filter,
                        ignoreCase: ignoreCase
                    };

                    that._filterSource(expression, that._retrieveData);
                    that._retrieveData = false;
                }
            },

            value: function (value) {
                var that = this,
                tags = $(that.tagList[0].children),
                length = tags.length,
                dataItemIndex,
                idx = 0;

                if (value === undefined) {
                    return that._values;
                }

                if (that._fetchItems(value)) {
                    return;
                }

                for (; idx < length; idx++) {
                    that._unselect(tags.eq(idx));
                }

                if (value !== null) {
                    value = isArray(value) || value instanceof ObservableArray ? value : [value];

                    for (idx = 0, length = value.length; idx < length; idx++) {
                        dataItemIndex = that._index(value[idx]);
                        if (dataItemIndex > -1) {
                            that._select(dataItemIndex);
                        }
                    }

                    that._old = that._values.slice();
                }
            },

            _dataSource: function () {
                var that = this,
                element = that.element,
                options = that.options,
                dataSource = options.dataSource || {};

                dataSource = isArray(dataSource) ? { data: dataSource} : dataSource;

                dataSource.select = element;
                dataSource.fields = [{ field: options.dataTextField },
                                 { field: options.dataValueField}];

                if (that.dataSource && that._refreshHandler) {
                    that._unbindDataSource();
                } else {
                    that._refreshHandler = proxy(that.refresh, that);
                    that._progressHandler = proxy(that._showBusy, that);
                }

                that.dataSource = kendo.data.DataSource.create(dataSource)
                                   .bind(CHANGE, that._refreshHandler)
                                   .bind(PROGRESS, that._progressHandler);
            },

            _fetchItems: function (value) {
                var that = this;
                var isEmptyArray = $.isArray(value) && value.length === 0;

                if (isEmptyArray || !value) {
                    return;
                }

                if (!that._fetch && !that.ul[0].firstChild) {
                    that.dataSource.one(CHANGE, function () {
                        that.value(value);
                        that._fetch = false;
                    });

                    that._fetch = true;
                    that.dataSource.fetch();

                    return true;
                }
            },

            _reset: function () {
                var that = this,
                element = that.element,
                formId = element.attr("form"),
                form = formId ? $("#" + formId) : element.closest("form");

                if (form[0]) {
                    that._resetHandler = function () {
                        setTimeout(function () {
                            that.value(that._initialValues);
                            that._placeholder();
                        });
                    };

                    that._form = form.on("reset", that._resetHandler);
                }
            },

            _initValue: function () {
                var that = this,
                value = that.options.value || that.element.val();

                if (value === null) {
                    value = [];
                } else {
                    if (!isArray(value)) {
                        value = [value];
                    }

                    value = that._mapValues(value);
                }

                that._old = that._initialValues = value;
                that._setInitialValues = value[0] !== undefined;
            },

            _mapValues: function (values) {
                var that = this;

                if (values && $.isPlainObject(values[0])) {
                    values = $.map(values, function (dataItem) { return that._value(dataItem); });
                }

                return values;
            },

            _change: function () {
                var that = this,
                value = that.value();

                if (!compare(value, that._old)) {
                    that._old = value.slice();

                    that.trigger(CHANGE);

                    // trigger the DOM change event so any subscriber gets notified
                    that.element.trigger(CHANGE);
                }
            },

            _click: function (e) {
                var that = this,
                li = $(e.currentTarget);

                if (!e.isDefaultPrevented()) {
                    if (that.trigger(SELECT, { item: li })) {
                        that._close();
                        return;
                    }

                    that._select(li);
                    that._change();
                    that._close();
                }
            },

            _item: function (item, direction) {
                item = item[direction]();

                if (item[0] && !item.is(":visible")) {
                    item = this._item(item, direction);
                }

                return item;
            },

            _keydown: function (e) {
                var that = this,
                key = e.keyCode,
                tag = that._currentTag,
                current = that._current,
                hasValue = that.input.val(),
                isRtl = kendo.support.isRtl(that.wrapper),
                visible = that.popup.visible();

                if (key === keys.DOWN) {
                    e.preventDefault();

                    if (!visible) {
                        that.open();
                        return;
                    }

                    if (current) {
                        current = sibling(current[0], NEXT);
                    } else {
                        current = first(that.ul[0]);
                    }

                    if (current) {
                        that.current($(current));
                    }
                } else if (key === keys.UP) {
                    if (visible) {
                        if (current) {
                            current = sibling(current[0], PREV);
                        } else {
                            current = last(that.ul[0]);
                        }

                        that.current($(current));

                        if (!that._current[0]) {
                            that.close();
                        }
                    }
                    e.preventDefault();
                } else if ((key === keys.LEFT && !isRtl) || (key === keys.RIGHT && isRtl)) {
                    if (!hasValue) {
                        tag = tag ? tag.prev() : $(that.tagList[0].lastChild);
                        if (tag[0]) {
                            that.currentTag(tag);
                        }
                    }
                } else if ((key === keys.RIGHT && !isRtl) || (key === keys.LEFT && isRtl)) {
                    if (!hasValue && tag) {
                        tag = tag.next();
                        that.currentTag(tag[0] ? tag : null);
                    }
                } else if (key === keys.ENTER && visible) {
                    if (current) {
                        if (that.trigger(SELECT, { item: current })) {
                            that._close();
                            return;
                        }
                        that._select(current);
                    }

                    that._change();
                    that._close();
                    e.preventDefault();
                } else if (key === keys.ESC) {
                    if (visible) {
                        e.preventDefault();
                    } else {
                        that.currentTag(null);
                    }

                    that.close();
                } else if (key === keys.HOME) {
                    if (visible) {
                        that.current(first(that.ul[0]));
                    } else if (!hasValue) {
                        tag = that.tagList[0].firstChild;

                        if (tag) {
                            that.currentTag($(tag));
                        }
                    }
                } else if (key === keys.END) {
                    if (visible) {
                        that.current(last(that.ul[0]));
                    } else if (!hasValue) {
                        tag = that.tagList[0].lastChild;

                        if (tag) {
                            that.currentTag($(tag));
                        }
                    }
                } else if ((key === keys.DELETE || key === keys.BACKSPACE) && !hasValue) {
                    if (key === keys.BACKSPACE && !tag) {
                        tag = $(that.tagList[0].lastChild);
                    }

                    if (tag && tag[0]) {
                        that._unselect(tag);
                        that._change();
                        that._close();
                    }
                } else {
                    clearTimeout(that._typing);
                    setTimeout(function () { that._scale(); });
                    that._search();
                }
            },

            _hideBusy: function () {
                var that = this;
                clearTimeout(that._busy);
                that.input.attr("aria-busy", false);
                that._loading.addClass(HIDDENCLASS);
                that._request = false;
                that._busy = null;
            },

            _showBusyHandler: function () {
                this.input.attr("aria-busy", true);
                this._loading.removeClass(HIDDENCLASS);
            },

            _showBusy: function () {
                var that = this;

                that._request = true;

                if (that._busy) {
                    return;
                }

                that._busy = setTimeout(proxy(that._showBusyHandler, that), 100);
            },

            _placeholder: function (show, skipCaret) {
                var that = this,
                input = that.input,
                active = activeElement();

                if (show === undefined) {
                    show = false;
                    if (input[0] !== active) {
                        show = !that._dataItems[0];
                    }
                }

                that._prev = "";
                input.toggleClass("k-readonly", show)
                 .val(show ? that.options.placeholder : "");

                if (input[0] === active && !skipCaret) {
                    kendo.caret(input[0], 0, 0);
                }

                that._scale();
            },

            _scale: function () {
                var that = this,
                wrapper = that.wrapper,
                wrapperWidth = wrapper.width(),
                span = that._span.text(that.input.val()),
                textWidth;

                if (!wrapper.is(":visible")) {
                    span.appendTo(document.documentElement);
                    wrapperWidth = textWidth = span.width() + 25;
                    span.appendTo(wrapper);
                } else {
                    textWidth = span.width() + 25;
                }

                that.input.width(textWidth > wrapperWidth ? wrapperWidth : textWidth);
            },

            _option: function (dataItem, selected) {
                var option = "<option",
                dataText = this._text(dataItem),
                dataValue = this._value(dataItem);

                if (dataValue !== undefined) {
                    dataValue += "";

                    if (dataValue.indexOf('"') !== -1) {
                        dataValue = dataValue.replace(quotRegExp, "&quot;");
                    }

                    option += ' value="' + dataValue + '"';
                }

                if (selected) {
                    option += ' selected="selected"';
                }

                option += ">";

                if (dataText !== undefined) {
                    option += kendo.htmlEncode(dataText);
                }

                return option += "</option>";
            },

            _render: function (data) {
                var that = this,
                length = data.length,
                template = that.itemTemplate,
                values = that._dataItems.slice(0),
                visibleItems = 0, idx = 0,
                options = "", html = "",
                dataItem, selected;

                for (; idx < length; idx++) {
                    dataItem = data[idx];
                    selected = that._selected(values, dataItem);

                    html += template(dataItem, idx, selected);
                    options += that._option(dataItem, selected);

                    if (!selected) {
                        visibleItems += 1;
                    }
                }

                length = values.length;
                if (length) {
                    for (idx = 0; idx < length; idx++) {
                        options += that._option(values[idx], true);
                    }
                }

                that.ul[0].innerHTML = html;
                that.element.html(options);
                that._visibleItems = visibleItems;

                return visibleItems;
            },

            _selected: function (values, dataItem) {
                var that = this,
                textAccessor = that._text,
                valueAccessor = that._value,
                value = valueAccessor(dataItem),
                length = values.length,
                selected = false,
                dataValue,
                idx = 0;

                if (value === undefined) {
                    value = textAccessor(dataItem);
                }

                for (; idx < length; idx++) {
                    dataItem = values[idx];
                    dataValue = valueAccessor(dataItem);

                    if (dataValue === undefined) {
                        dataValue = textAccessor(dataItem);
                    }

                    if (dataValue !== undefined && dataValue === value) {
                        selected = true;
                        break;
                    }
                }

                if (selected) {
                    values.splice(idx, 1);
                }

                return selected;
            },

            _search: function () {
                var that = this;

                that._typing = setTimeout(function () {
                    var value = that.input.val();
                    if (that._prev !== value) {
                        that._prev = value;
                        that.search(value);
                    }
                }, that.options.delay);
            },

            _allowSelection: function () {
                var max = this.options.maxSelectedItems;
                return max === null || max > this._values.length;
            },

            _select: function (li) {
                var that = this,
                values = that._values,
                dataItem,
                idx;

                if (!that._allowSelection()) {
                    return;
                }

                if (!isNaN(li)) {
                    idx = li;
                    that.ul[0].children[idx].style.display = "none";
                } else {
                    idx = li.hide().data("idx");
                }

                that.element[0].children[idx].selected = true;

                dataItem = that.dataSource.view()[idx];

                that.tagList.append(that.tagTemplate(dataItem));
                that._dataItems.push(dataItem);
                values.push(that._dataValue(dataItem));

                that._visibleItems -= 1;
                that.currentTag(null);
                that._placeholder();
                that._height(that._visibleItems);

                if (that._state === FILTER) {
                    that._state = ACCEPT;
                }
            },

            _unselect: function (tag) {
                var that = this,
                index = tag.index(),
                dataItem, value,
                options, option, length;

                tag.remove();
                that.currentTag(null);

                that._values.splice(index, 1);
                dataItem = that._dataItems.splice(index, 1)[0];

                value = that._dataValue(dataItem);
                index = that._index(value);

                if (index !== -1) {
                    $(that.ul[0].children[index]).show();
                    that.element[0].children[index].selected = false;
                    that._visibleItems += 1;
                    that._height(that._visibleItems);
                } else {
                    index = that.dataSource.view().length;
                    options = that.element[0].children;
                    length = options.length;

                    for (; index < length; index++) {
                        option = options[index];
                        if (option.value == value) {
                            option.selected = false;
                            break;
                        }
                    }
                }

                that._placeholder();
            },

            _template: function () {
                var that = this,
                options = that.options,
                itemTemplate = options.itemTemplate,
                tagTemplate = options.tagTemplate,
                hasDataSource = options.dataSource,
                textTemplate;

                if (that.element[0].length && !hasDataSource) {
                    options.dataTextField = options.dataTextField || "text";
                    options.dataValueField = options.dataValueField || "value";
                }

                textTemplate = kendo.template("#:" + kendo.expr(options.dataTextField, "data") + "#", { useWithBlock: false });

                itemTemplate = itemTemplate ? kendo.template(itemTemplate) : textTemplate;
                tagTemplate = tagTemplate ? kendo.template(tagTemplate) : textTemplate;

                that.itemTemplate = function (data, idx, hide) {
                    return '<li tabindex="-1" role="option" data-idx="' + idx + '" unselectable="on" class="k-item"' + (hide ? HIDE : "") + '>' + itemTemplate(data) + '</li>';
                };

                that.tagTemplate = function (data) {
                    return '<li class="k-button" unselectable="on"><span unselectable="on">' + tagTemplate(data) + '</span><span unselectable="on" class="k-icon k-delete">delete</span></li>';
                };
            },

            _input: function () {
                var that = this,
                accessKey = that.element[0].accessKey,
                input = that._innerWrapper.children("input.k-input");

                if (!input[0]) {
                    input = $('<input class="k-input" style="width: 25px" />').appendTo(that._innerWrapper);
                }

                that.element.removeAttr("accesskey");
                that._focused = that.input = input.attr({
                    "accesskey": accessKey,
                    "autocomplete": "off",
                    "role": "listbox",
                    "aria-expanded": false
                });
            },

            _tagList: function () {
                var that = this,
                tagList = that._innerWrapper.children("ul");

                if (!tagList[0]) {
                    tagList = $('<ul role="listbox" unselectable="on" class="k-reset"/>').appendTo(that._innerWrapper);
                }

                that.tagList = tagList;
            },

            _loader: function () {
                this._loading = $('<span class="k-icon k-loading ' + HIDDENCLASS + '"></span>').insertAfter(this.input);
            },

            _textContainer: function () {
                var computedStyles = kendo.getComputedStyles(this.input[0], styles);

                computedStyles.position = "absolute";
                computedStyles.visibility = "hidden";
                computedStyles.top = -3333;
                computedStyles.left = -3333;

                this._span = $("<span/>").css(computedStyles).appendTo(this.wrapper);
            },

            _wrapper: function () {
                var that = this,
                element = that.element,
                wrapper = element.parent("span.k-multiselect");

                if (!wrapper[0]) {
                    wrapper = element.wrap('<div class="k-widget k-multiselect k-header" unselectable="on" />').parent();
                    wrapper[0].style.cssText = element[0].style.cssText;

                    $('<div class="k-multiselect-wrap k-floatwrap" unselectable="on" />').insertBefore(element);
                }

                that.wrapper = wrapper.addClass(element[0].className).css("display", "");
                that._innerWrapper = $(wrapper[0].firstChild);
            }
        });

        function compare(a, b) {
            var length;

            if ((a === null && b !== null) || (a !== null && b === null)) {
                return false;
            }

            length = a.length;
            if (length !== b.length) {
                return false;
            }

            while (length--) {
                if (a[length] !== b[length]) {
                    return false;
                }
            }

            return true;
        }

        function first(ul) {
            var item = ul.firstChild;

            if (item && item.style.display === "none") {
                item = sibling(item, NEXT);
            }

            if (item) {
                return $(item);
            }

            return item;
        }

        function last(ul) {
            var item = ul.lastChild;

            if (item && item.style.display === "none") {
                item = sibling(item, PREV);
            }

            if (item) {
                return $(item);
            }

            return item;
        }

        function sibling(item, direction) {
            item = item[direction];

            if (item && item.style.display === "none") {
                item = sibling(item, direction);
            }

            return item;
        }

        ui.plugin(MultiSelect);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        Draggable = kendo.ui.Draggable,
        extend = $.extend,
        format = kendo.format,
        parse = kendo.parseFloat,
        proxy = $.proxy,
        isArray = $.isArray,
        math = Math,
        support = kendo.support,
        pointers = support.pointers,
        msPointers = support.msPointers,
        CHANGE = "change",
        SLIDE = "slide",
        NS = ".slider",
        MOUSE_DOWN = "touchstart" + NS + " mousedown" + NS,
        TRACK_MOUSE_DOWN = pointers ? "pointerdown" + NS : (msPointers ? "MSPointerDown" + NS : MOUSE_DOWN),
        MOUSE_UP = "touchend" + NS + " mouseup" + NS,
        TRACK_MOUSE_UP = pointers ? "pointerup" : (msPointers ? "MSPointerUp" + NS : MOUSE_UP),
        MOVE_SELECTION = "moveSelection",
        KEY_DOWN = "keydown" + NS,
        CLICK = "click" + NS,
        MOUSE_OVER = "mouseover" + NS,
        FOCUS = "focus" + NS,
        BLUR = "blur" + NS,
        DRAG_HANDLE = ".k-draghandle",
        TRACK_SELECTOR = ".k-slider-track",
        TICK_SELECTOR = ".k-tick",
        STATE_SELECTED = "k-state-selected",
        STATE_FOCUSED = "k-state-focused",
        STATE_DEFAULT = "k-state-default",
        STATE_DISABLED = "k-state-disabled",
        PRECISION = 3,
        DISABLED = "disabled",
        UNDEFINED = "undefined",
        TABINDEX = "tabindex",
        getTouches = kendo.getTouches;

        var SliderBase = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                options = that.options;

                that._distance = round(options.max - options.min);
                that._isHorizontal = options.orientation == "horizontal";
                that._isRtl = that._isHorizontal && kendo.support.isRtl(element);
                that._position = that._isHorizontal ? "left" : "bottom";
                that._sizeFn = that._isHorizontal ? "width" : "height";
                that._outerSize = that._isHorizontal ? "outerWidth" : "outerHeight";

                options.tooltip.format = options.tooltip.enabled ? options.tooltip.format || "{0}" : "{0}";

                that._createHtml();
                that.wrapper = that.element.closest(".k-slider");
                that._trackDiv = that.wrapper.find(TRACK_SELECTOR);

                that._setTrackDivWidth();

                that._maxSelection = that._trackDiv[that._sizeFn]();

                that._sliderItemsInit();

                that._tabindex(that.wrapper.find(DRAG_HANDLE));
                that[options.enabled ? "enable" : "disable"]();

                var rtlDirectionSign = kendo.support.isRtl(that.wrapper) ? -1 : 1;

                that._keyMap = {
                    37: step(-1 * rtlDirectionSign * options.smallStep), // left arrow
                    40: step(-options.smallStep), // down arrow
                    39: step(+1 * rtlDirectionSign * options.smallStep), // right arrow
                    38: step(+options.smallStep), // up arrow
                    35: setValue(options.max), // end
                    36: setValue(options.min), // home
                    33: step(+options.largeStep), // page up
                    34: step(-options.largeStep)  // page down
                };

                kendo.notify(that);
            },

            events: [
            CHANGE,
            SLIDE
        ],

            options: {
                enabled: true,
                min: 0,
                max: 10,
                smallStep: 1,
                largeStep: 5,
                orientation: "horizontal",
                tickPlacement: "both",
                tooltip: { enabled: true, format: "{0}" }
            },

            _resize: function () {
                this._setTrackDivWidth();
                this.wrapper.find(".k-slider-items").remove();

                this._maxSelection = this._trackDiv[this._sizeFn]();
                this._sliderItemsInit();
                this._refresh();
            },

            _sliderItemsInit: function () {
                var that = this,
                options = that.options;

                var sizeBetweenTicks = that._maxSelection / ((options.max - options.min) / options.smallStep);
                var pixelWidths = that._calculateItemsWidth(math.floor(that._distance / options.smallStep));

                if (options.tickPlacement != "none" && sizeBetweenTicks >= 2) {
                    that._trackDiv.before(createSliderItems(options, that._distance));
                    that._setItemsWidth(pixelWidths);
                    that._setItemsTitle();
                }

                that._calculateSteps(pixelWidths);

                if (options.tickPlacement != "none" && sizeBetweenTicks >= 2 &&
                options.largeStep >= options.smallStep) {
                    that._setItemsLargeTick();
                }
            },

            getSize: function () {
                return kendo.dimensions(this.wrapper);
            },

            _setTrackDivWidth: function () {
                var that = this,
                trackDivPosition = parseFloat(that._trackDiv.css(that._isRtl ? "right" : that._position), 10) * 2;

                that._trackDiv[that._sizeFn]((that.wrapper[that._sizeFn]() - 2) - trackDivPosition);
            },

            _setItemsWidth: function (pixelWidths) {
                var that = this,
                options = that.options,
                first = 0,
                last = pixelWidths.length - 1,
                items = that.wrapper.find(TICK_SELECTOR),
                i,
                paddingTop = 0,
                bordersWidth = 2,
                count = items.length,
                selection = 0;

                for (i = 0; i < count - 2; i++) {
                    $(items[i + 1])[that._sizeFn](pixelWidths[i]);
                }

                if (that._isHorizontal) {
                    $(items[first]).addClass("k-first")[that._sizeFn](pixelWidths[last - 1]);
                    $(items[last]).addClass("k-last")[that._sizeFn](pixelWidths[last]);
                } else {
                    $(items[last]).addClass("k-first")[that._sizeFn](pixelWidths[last]);
                    $(items[first]).addClass("k-last")[that._sizeFn](pixelWidths[last - 1]);
                }

                if (that._distance % options.smallStep !== 0 && !that._isHorizontal) {
                    for (i = 0; i < pixelWidths.length; i++) {
                        selection += pixelWidths[i];
                    }

                    paddingTop = that._maxSelection - selection;
                    paddingTop += parseFloat(that._trackDiv.css(that._position), 10) + bordersWidth;

                    that.wrapper.find(".k-slider-items").css("padding-top", paddingTop);
                }
            },

            _setItemsTitle: function () {
                var that = this,
                options = that.options,
                items = that.wrapper.find(TICK_SELECTOR),
                titleNumber = options.min,
                count = items.length,
                i = that._isHorizontal && !that._isRtl ? 0 : count - 1,
                limit = that._isHorizontal && !that._isRtl ? count : -1,
                increment = that._isHorizontal && !that._isRtl ? 1 : -1;

                for (; i - limit !== 0; i += increment) {
                    $(items[i]).attr("title", format(options.tooltip.format, round(titleNumber)));
                    titleNumber += options.smallStep;
                }
            },

            _setItemsLargeTick: function () {
                var that = this,
                options = that.options,
                items = that.wrapper.find(TICK_SELECTOR),
                i = 0, item, value;

                if (removeFraction(options.largeStep) % removeFraction(options.smallStep) === 0 || that._distance / options.largeStep >= 3) {
                    if (!that._isHorizontal && !that._isRtl) {
                        items = $.makeArray(items).reverse();
                    }

                    for (i = 0; i < items.length; i++) {
                        item = $(items[i]);
                        value = that._values[i];
                        var valueWithoutFraction = removeFraction(value - this.options.min);
                        if (valueWithoutFraction % removeFraction(options.smallStep) === 0 && valueWithoutFraction % removeFraction(options.largeStep) === 0) {
                            item.addClass("k-tick-large")
                            .html("<span class='k-label'>" + item.attr("title") + "</span>");

                            if (i !== 0 && i !== items.length - 1) {
                                item.css("line-height", item[that._sizeFn]() + "px");
                            }
                        }
                    }
                }
            },

            _calculateItemsWidth: function (itemsCount) {
                var that = this,
                options = that.options,
                trackDivSize = parseFloat(that._trackDiv.css(that._sizeFn)) + 1,
                pixelStep = trackDivSize / that._distance,
                itemWidth,
                pixelWidths,
                i;

                if ((that._distance / options.smallStep) - math.floor(that._distance / options.smallStep) > 0) {
                    trackDivSize -= ((that._distance % options.smallStep) * pixelStep);
                }

                itemWidth = trackDivSize / itemsCount;
                pixelWidths = [];

                for (i = 0; i < itemsCount - 1; i++) {
                    pixelWidths[i] = itemWidth;
                }

                pixelWidths[itemsCount - 1] = pixelWidths[itemsCount] = itemWidth / 2;
                return that._roundWidths(pixelWidths);
            },

            _roundWidths: function (pixelWidthsArray) {
                var balance = 0,
                count = pixelWidthsArray.length,
                i;

                for (i = 0; i < count; i++) {
                    balance += (pixelWidthsArray[i] - math.floor(pixelWidthsArray[i]));
                    pixelWidthsArray[i] = math.floor(pixelWidthsArray[i]);
                }

                balance = math.round(balance);

                return this._addAdditionalSize(balance, pixelWidthsArray);
            },

            _addAdditionalSize: function (additionalSize, pixelWidthsArray) {
                if (additionalSize === 0) {
                    return pixelWidthsArray;
                }

                //set step size
                var step = parseFloat(pixelWidthsArray.length - 1) / parseFloat(additionalSize == 1 ? additionalSize : additionalSize - 1),
                i;

                for (i = 0; i < additionalSize; i++) {
                    pixelWidthsArray[parseInt(math.round(step * i), 10)] += 1;
                }

                return pixelWidthsArray;
            },

            _calculateSteps: function (pixelWidths) {
                var that = this,
                options = that.options,
                val = options.min,
                selection = 0,
                itemsCount = math.ceil(that._distance / options.smallStep),
                i = 1,
                lastItem;

                itemsCount += (that._distance / options.smallStep) % 1 === 0 ? 1 : 0;
                pixelWidths.splice(0, 0, pixelWidths[itemsCount - 2] * 2);
                pixelWidths.splice(itemsCount - 1, 1, pixelWidths.pop() * 2);

                that._pixelSteps = [selection];
                that._values = [val];

                if (itemsCount === 0) {
                    return;
                }

                while (i < itemsCount) {
                    selection += (pixelWidths[i - 1] + pixelWidths[i]) / 2;
                    that._pixelSteps[i] = selection;
                    val += options.smallStep;
                    that._values[i] = round(val);

                    i++;
                }

                lastItem = that._distance % options.smallStep === 0 ? itemsCount - 1 : itemsCount;

                that._pixelSteps[lastItem] = that._maxSelection;
                that._values[lastItem] = options.max;

                if (that._isRtl) {
                    that._pixelSteps.reverse();
                    that._values.reverse();
                }
            },

            _getValueFromPosition: function (mousePosition, dragableArea) {
                var that = this,
                options = that.options,
                step = math.max(options.smallStep * (that._maxSelection / that._distance), 0),
                position = 0,
                halfStep = (step / 2),
                i;

                if (that._isHorizontal) {
                    position = mousePosition - dragableArea.startPoint;
                    if (that._isRtl) {
                        position = that._maxSelection - position;
                    }
                } else {
                    position = dragableArea.startPoint - mousePosition;
                }

                if (that._maxSelection - ((parseInt(that._maxSelection % step, 10) - 3) / 2) < position) {
                    return options.max;
                }

                for (i = 0; i < that._pixelSteps.length; i++) {
                    if (math.abs(that._pixelSteps[i] - position) - 1 <= halfStep) {
                        return round(that._values[i]);
                    }
                }
            },

            _getFormattedValue: function (val, drag) {
                var that = this,
                html = "",
                tooltip = that.options.tooltip,
                tooltipTemplate,
                selectionStart,
                selectionEnd;

                if (isArray(val)) {
                    selectionStart = val[0];
                    selectionEnd = val[1];
                } else if (drag && drag.type) {
                    selectionStart = drag.selectionStart;
                    selectionEnd = drag.selectionEnd;
                }

                if (drag) {
                    tooltipTemplate = drag.tooltipTemplate;
                }

                if (!tooltipTemplate && tooltip.template) {
                    tooltipTemplate = kendo.template(tooltip.template);
                }

                if (isArray(val) || (drag && drag.type)) {

                    if (tooltipTemplate) {
                        html = tooltipTemplate({
                            selectionStart: selectionStart,
                            selectionEnd: selectionEnd
                        });
                    } else {
                        selectionStart = format(tooltip.format, selectionStart);
                        selectionEnd = format(tooltip.format, selectionEnd);
                        html = selectionStart + " - " + selectionEnd;
                    }
                } else {
                    if (drag) {
                        drag.val = val;
                    }

                    if (tooltipTemplate) {
                        html = tooltipTemplate({
                            value: val
                        });
                    } else {
                        html = format(tooltip.format, val);
                    }
                }
                return html;
            },

            _getDraggableArea: function () {
                var that = this,
                offset = kendo.getOffset(that._trackDiv);

                return {
                    startPoint: that._isHorizontal ? offset.left : offset.top + that._maxSelection,
                    endPoint: that._isHorizontal ? offset.left + that._maxSelection : offset.top
                };
            },

            _createHtml: function () {
                var that = this,
                element = that.element,
                options = that.options,
                inputs = element.find("input");

                if (inputs.length == 2) {
                    inputs.eq(0).prop("value", formatValue(options.selectionStart));
                    inputs.eq(1).prop("value", formatValue(options.selectionEnd));
                } else {
                    element.prop("value", formatValue(options.value));
                }

                element.wrap(createWrapper(options, element, that._isHorizontal)).hide();

                if (options.showButtons) {
                    element.before(createButton(options, "increase", that._isHorizontal))
                       .before(createButton(options, "decrease", that._isHorizontal));
                }

                element.before(createTrack(options, element));
            },

            _focus: function (e) {
                var that = this,
                target = e.target,
                val = that.value(),
                drag = that._drag;

                if (!drag) {
                    if (target == that.wrapper.find(DRAG_HANDLE).eq(0)[0]) {
                        drag = that._firstHandleDrag;
                        that._activeHandle = 0;
                    } else {
                        drag = that._lastHandleDrag;
                        that._activeHandle = 1;
                    }
                    val = val[that._activeHandle];
                }

                $(target).addClass(STATE_FOCUSED + " " + STATE_SELECTED);

                if (drag) {
                    that._activeHandleDrag = drag;

                    drag.selectionStart = that.options.selectionStart;
                    drag.selectionEnd = that.options.selectionEnd;

                    drag._updateTooltip(val);
                }
            },

            _focusWithMouse: function (target) {
                target = $(target);

                var that = this,
                idx = target.is(DRAG_HANDLE) ? target.index() : 0;

                window.setTimeout(function () {
                    that.wrapper.find(DRAG_HANDLE)[idx == 2 ? 1 : 0].focus();
                }, 1);

                that._setTooltipTimeout();
            },

            _blur: function (e) {
                var that = this,
                drag = that._activeHandleDrag;

                $(e.target).removeClass(STATE_FOCUSED + " " + STATE_SELECTED);

                if (drag) {
                    drag._removeTooltip();
                    delete that._activeHandleDrag;
                    delete that._activeHandle;
                }
            },

            _setTooltipTimeout: function () {
                var that = this;
                that._tooltipTimeout = window.setTimeout(function () {
                    var drag = that._drag || that._activeHandleDrag;
                    if (drag) {
                        drag._removeTooltip();
                    }
                }, 300);
            },

            _clearTooltipTimeout: function () {
                var that = this;
                window.clearTimeout(this._tooltipTimeout);
                var drag = that._drag || that._activeHandleDrag;
                if (drag && drag.tooltipDiv) {
                    drag.tooltipDiv.stop(true, false).css("opacity", 1);
                }
            }
        });

        function createWrapper(options, element, isHorizontal) {
            var orientationCssClass = isHorizontal ? " k-slider-horizontal" : " k-slider-vertical",
            style = options.style ? options.style : element.attr("style"),
            cssClasses = element.attr("class") ? (" " + element.attr("class")) : "",
            tickPlacementCssClass = "";

            if (options.tickPlacement == "bottomRight") {
                tickPlacementCssClass = " k-slider-bottomright";
            } else if (options.tickPlacement == "topLeft") {
                tickPlacementCssClass = " k-slider-topleft";
            }

            style = style ? " style='" + style + "'" : "";

            return "<div class='k-widget k-slider" + orientationCssClass + cssClasses + "'" + style + ">" +
               "<div class='k-slider-wrap" + (options.showButtons ? " k-slider-buttons" : "") + tickPlacementCssClass +
               "'></div></div>";
        }

        function createButton(options, type, isHorizontal) {
            var buttonCssClass = "";

            if (type == "increase") {
                buttonCssClass = isHorizontal ? "k-i-arrow-e" : "k-i-arrow-n";
            } else {
                buttonCssClass = isHorizontal ? "k-i-arrow-w" : "k-i-arrow-s";
            }

            return "<a class='k-button k-button-" + type + "'><span class='k-icon " + buttonCssClass +
               "' title='" + options[type + "ButtonTitle"] + "'>" + options[type + "ButtonTitle"] + "</span></a>";
        }

        function createSliderItems(options, distance) {
            var result = "<ul class='k-reset k-slider-items'>",
            count = math.floor(round(distance / options.smallStep)) + 1,
            i;

            for (i = 0; i < count; i++) {
                result += "<li class='k-tick' role='presentation'>&nbsp;</li>";
            }

            result += "</ul>";

            return result;
        }

        function createTrack(options, element) {
            var dragHandleCount = element.is("input") ? 1 : 2,
            firstDragHandleTitle = dragHandleCount == 2 ? options.leftDragHandleTitle : options.dragHandleTitle;

            return "<div class='k-slider-track'><div class='k-slider-selection'><!-- --></div>" +
               "<a href='#' class='k-draghandle' title='" + firstDragHandleTitle + "' role='slider' aria-valuemin='" + options.min + "' aria-valuemax='" + options.max + "' aria-valuenow='" + (dragHandleCount > 1 ? (options.selectionStart || options.min) : options.value || options.min) + "'>Drag</a>" +
               (dragHandleCount > 1 ? "<a href='#' class='k-draghandle' title='" + options.rightDragHandleTitle + "'role='slider' aria-valuemin='" + options.min + "' aria-valuemax='" + options.max + "' aria-valuenow='" + (options.selectionEnd || options.max) + "'>Drag</a>" : "") +
               "</div>";
        }

        function step(stepValue) {
            return function (value) {
                return value + stepValue;
            };
        }

        function setValue(value) {
            return function () {
                return value;
            };
        }

        function formatValue(value) {
            return (value + "").replace(".", kendo.cultures.current.numberFormat["."]);
        }

        function round(value) {
            value = parseFloat(value, 10);
            var power = math.pow(10, PRECISION || 0);
            return math.round(value * power) / power;
        }

        function parseAttr(element, name) {
            var value = parse(element.getAttribute(name));
            if (value === null) {
                value = undefined;
            }
            return value;
        }

        function defined(value) {
            return typeof value !== UNDEFINED;
        }

        function removeFraction(value) {
            return value * 10000;
        }

        var Slider = SliderBase.extend({
            init: function (element, options) {
                var that = this,
                dragHandle;

                element.type = "text";
                options = extend({}, {
                    value: parseAttr(element, "value"),
                    min: parseAttr(element, "min"),
                    max: parseAttr(element, "max"),
                    smallStep: parseAttr(element, "step")
                }, options);

                element = $(element);

                if (options && options.enabled === undefined) {
                    options.enabled = !element.is("[disabled]");
                }

                SliderBase.fn.init.call(that, element, options);
                options = that.options;
                if (!defined(options.value) || options.value === null) {
                    options.value = options.min;
                    element.prop("value", formatValue(options.min));
                }
                options.value = math.max(math.min(options.value, options.max), options.min);

                dragHandle = that.wrapper.find(DRAG_HANDLE);

                new Slider.Selection(dragHandle, that, options);
                that._drag = new Slider.Drag(dragHandle, "", that, options);
            },

            options: {
                name: "Slider",
                showButtons: true,
                increaseButtonTitle: "Increase",
                decreaseButtonTitle: "Decrease",
                dragHandleTitle: "drag",
                tooltip: { format: "{0:#,#.##}" },
                value: null
            },

            enable: function (enable) {
                var that = this,
                options = that.options,
                clickHandler,
                move;

                that.disable();
                if (enable === false) {
                    return;
                }

                that.wrapper
                .removeClass(STATE_DISABLED)
                .addClass(STATE_DEFAULT);

                that.wrapper.find("input").removeAttr(DISABLED);

                clickHandler = function (e) {
                    var touch = getTouches(e)[0];

                    if (!touch) {
                        return;
                    }

                    var mousePosition = that._isHorizontal ? touch.location.pageX : touch.location.pageY,
                    dragableArea = that._getDraggableArea(),
                    target = $(e.target);

                    if (target.hasClass("k-draghandle")) {
                        target.addClass(STATE_FOCUSED + " " + STATE_SELECTED);
                        return;
                    }

                    that._update(that._getValueFromPosition(mousePosition, dragableArea));

                    that._focusWithMouse(e.target);

                    that._drag.dragstart(e);
                    e.preventDefault();
                };

                that.wrapper
                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR)
                    .on(TRACK_MOUSE_DOWN, clickHandler)
                    .end()
                    .on(TRACK_MOUSE_DOWN, function () {
                        $(document.documentElement).one("selectstart", kendo.preventDefault);
                    })
                    .on(TRACK_MOUSE_UP, function () {
                        that._drag._end();
                    });

                that.wrapper
                .find(DRAG_HANDLE)
                .attr(TABINDEX, 0)
                .on(MOUSE_UP, function () {
                    that._setTooltipTimeout();
                })
                .on(CLICK, function (e) {
                    that._focusWithMouse(e.target);
                    e.preventDefault();
                })
                .on(FOCUS, proxy(that._focus, that))
                .on(BLUR, proxy(that._blur, that));

                move = proxy(function (sign) {
                    var newVal = that._nextValueByIndex(that._valueIndex + (sign * 1));
                    that._setValueInRange(newVal);
                    that._drag._updateTooltip(newVal);
                }, that);

                if (options.showButtons) {
                    var mouseDownHandler = proxy(function (e, sign) {
                        this._clearTooltipTimeout();
                        if (e.which === 1 || (support.touch && e.which === 0)) {
                            move(sign);

                            this.timeout = setTimeout(proxy(function () {
                                this.timer = setInterval(function () {
                                    move(sign);
                                }, 60);
                            }, this), 200);
                        }
                    }, that);

                    that.wrapper.find(".k-button")
                    .on(MOUSE_UP, proxy(function (e) {
                        this._clearTimer();
                        that._focusWithMouse(e.target);
                    }, that))
                    .on(MOUSE_OVER, function (e) {
                        $(e.currentTarget).addClass("k-state-hover");
                    })
                    .on("mouseout" + NS, proxy(function (e) {
                        $(e.currentTarget).removeClass("k-state-hover");
                        this._clearTimer();
                    }, that))
                    .eq(0)
                    .on(MOUSE_DOWN, proxy(function (e) {
                        mouseDownHandler(e, 1);
                    }, that))
                    .click(false)
                    .end()
                    .eq(1)
                    .on(MOUSE_DOWN, proxy(function (e) {
                        mouseDownHandler(e, -1);
                    }, that))
                    .click(kendo.preventDefault);
                }

                that.wrapper
                .find(DRAG_HANDLE)
                .off(KEY_DOWN, false)
                .on(KEY_DOWN, proxy(this._keydown, that));

                options.enabled = true;
            },

            disable: function () {
                var that = this;

                that.wrapper
                .removeClass(STATE_DEFAULT)
                .addClass(STATE_DISABLED);

                $(that.element).prop(DISABLED, DISABLED);

                that.wrapper
                .find(".k-button")
                .off(MOUSE_DOWN)
                .on(MOUSE_DOWN, kendo.preventDefault)
                .off(MOUSE_UP)
                .on(MOUSE_UP, kendo.preventDefault)
                .off("mouseleave" + NS)
                .on("mouseleave" + NS, kendo.preventDefault)
                .off(MOUSE_OVER)
                .on(MOUSE_OVER, kendo.preventDefault);

                that.wrapper
                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR).off(TRACK_MOUSE_DOWN).off(TRACK_MOUSE_UP);

                that.wrapper
                .find(DRAG_HANDLE)
                .attr(TABINDEX, -1)
                .off(MOUSE_UP)
                .off(KEY_DOWN)
                .off(CLICK)
                .off(FOCUS)
                .off(BLUR);

                that.options.enabled = false;
            },

            _update: function (val) {
                var that = this,
                change = that.value() != val;

                that.value(val);

                if (change) {
                    that.trigger(CHANGE, { value: that.options.value });
                }
            },

            value: function (value) {
                var that = this,
                options = that.options;

                value = round(value);
                if (isNaN(value)) {
                    return options.value;
                }

                if (value >= options.min && value <= options.max) {
                    if (options.value != value) {
                        that.element.prop("value", formatValue(value));
                        options.value = value;
                        that._refreshAriaAttr(value);
                        that._refresh();
                    }
                }
            },

            _refresh: function () {
                this.trigger(MOVE_SELECTION, { value: this.options.value });
            },

            _refreshAriaAttr: function (value) {
                var that = this,
                drag = that._drag,
                formattedValue;

                if (drag && drag._tooltipDiv) {
                    formattedValue = drag._tooltipDiv.text();
                } else {
                    formattedValue = that._getFormattedValue(value, null);
                }
                this.wrapper.find(DRAG_HANDLE).attr("aria-valuenow", value).attr("aria-valuetext", formattedValue);
            },

            _clearTimer: function () {
                clearTimeout(this.timeout);
                clearInterval(this.timer);
            },

            _keydown: function (e) {
                var that = this;

                if (e.keyCode in that._keyMap) {
                    that._clearTooltipTimeout();
                    that._setValueInRange(that._keyMap[e.keyCode](that.options.value));
                    that._drag._updateTooltip(that.value());
                    e.preventDefault();
                }
            },

            _setValueInRange: function (val) {
                var that = this,
                options = that.options;

                val = round(val);
                if (isNaN(val)) {
                    that._update(options.min);
                    return;
                }

                val = math.max(math.min(val, options.max), options.min);
                that._update(val);
            },

            _nextValueByIndex: function (index) {
                var count = this._values.length;
                if (this._isRtl) {
                    index = count - 1 - index;
                }
                return this._values[math.max(0, math.min(index, count - 1))];
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.wrapper.off(NS)
                .find(".k-button").off(NS)
                .end()
                .find(DRAG_HANDLE).off(NS)
                .end()
                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR).off(NS)
                .end();

                that._drag.draggable.destroy();
                that._drag._removeTooltip(true);
            }
        });

        Slider.Selection = function (dragHandle, that, options) {
            function moveSelection(val) {
                var selectionValue = val - options.min,
                index = that._valueIndex = math.ceil(round(selectionValue / options.smallStep)),
                selection = parseInt(that._pixelSteps[index], 10),
                selectionDiv = that._trackDiv.find(".k-slider-selection"),
                halfDragHanndle = parseInt(dragHandle[that._outerSize]() / 2, 10),
                rtlCorrection = that._isRtl ? 2 : 0;

                selectionDiv[that._sizeFn](that._isRtl ? that._maxSelection - selection : selection);
                dragHandle.css(that._position, selection - halfDragHanndle - rtlCorrection);
            }

            moveSelection(options.value);

            that.bind([CHANGE, SLIDE, MOVE_SELECTION], function (e) {
                moveSelection(parseFloat(e.value, 10));
            });
        };

        Slider.Drag = function (element, type, owner, options) {
            var that = this;
            that.owner = owner;
            that.options = options;
            that.element = element;
            that.type = type;

            that.draggable = new Draggable(element, {
                distance: 0,
                dragstart: proxy(that._dragstart, that),
                drag: proxy(that.drag, that),
                dragend: proxy(that.dragend, that),
                dragcancel: proxy(that.dragcancel, that)
            });

            element.click(false);
        };

        Slider.Drag.prototype = {
            dragstart: function (e) {
                // add reference to the last active drag handle.
                this.owner._activeDragHandle = this;
                // HACK to initiate click on the line
                this.draggable.userEvents.cancel();
                this.draggable.userEvents._start(e);
            },

            _dragstart: function (e) {
                var that = this,
                owner = that.owner,
                options = that.options;

                if (!options.enabled) {
                    e.preventDefault();
                    return;
                }

                // add reference to the last active drag handle.
                this.owner._activeDragHandle = this;

                owner.element.off(MOUSE_OVER);
                owner.wrapper.find("." + STATE_FOCUSED).removeClass(STATE_FOCUSED + " " + STATE_SELECTED);
                that.element.addClass(STATE_FOCUSED + " " + STATE_SELECTED);
                $(document.documentElement).css("cursor", "pointer");

                that.dragableArea = owner._getDraggableArea();
                that.step = math.max(options.smallStep * (owner._maxSelection / owner._distance), 0);

                if (that.type) {
                    that.selectionStart = options.selectionStart;
                    that.selectionEnd = options.selectionEnd;
                    owner._setZIndex(that.type);
                } else {
                    that.oldVal = that.val = options.value;
                }

                that._removeTooltip(true);
                that._createTooltip();
            },

            _createTooltip: function () {
                var that = this,
                owner = that.owner,
                tooltip = that.options.tooltip,
                html = '',
                wnd = $(window),
                tooltipTemplate, colloutCssClass;

                if (!tooltip.enabled) {
                    return;
                }

                if (tooltip.template) {
                    tooltipTemplate = that.tooltipTemplate = kendo.template(tooltip.template);
                }

                $(".k-slider-tooltip").remove(); // if user changes window while tooltip is visible, a second one will be created
                that.tooltipDiv = $("<div class='k-widget k-tooltip k-slider-tooltip'><!-- --></div>").appendTo(document.body);

                html = owner._getFormattedValue(that.val || owner.value(), that);

                if (!that.type) {
                    colloutCssClass = "k-callout-" + (owner._isHorizontal ? 's' : 'e');
                    that.tooltipInnerDiv = "<div class='k-callout " + colloutCssClass + "'><!-- --></div>";
                    html += that.tooltipInnerDiv;
                }

                that.tooltipDiv.html(html);

                that._scrollOffset = {
                    top: wnd.scrollTop(),
                    left: wnd.scrollLeft()
                };

                that.moveTooltip();
            },

            drag: function (e) {
                var that = this,
                owner = that.owner,
                x = e.x.location,
                y = e.y.location,
                startPoint = that.dragableArea.startPoint,
                endPoint = that.dragableArea.endPoint,
                slideParams;

                e.preventDefault();

                if (owner._isHorizontal) {
                    if (owner._isRtl) {
                        that.val = that.constrainValue(x, startPoint, endPoint, x < endPoint);
                    } else {
                        that.val = that.constrainValue(x, startPoint, endPoint, x >= endPoint);
                    }
                } else {
                    that.val = that.constrainValue(y, endPoint, startPoint, y <= endPoint);
                }

                if (that.oldVal != that.val) {
                    that.oldVal = that.val;

                    if (that.type) {
                        if (that.type == "firstHandle") {
                            if (that.val < that.selectionEnd) {
                                that.selectionStart = that.val;
                            } else {
                                that.selectionStart = that.selectionEnd = that.val;
                            }
                        } else {
                            if (that.val > that.selectionStart) {
                                that.selectionEnd = that.val;
                            } else {
                                that.selectionStart = that.selectionEnd = that.val;
                            }
                        }
                        slideParams = {
                            values: [that.selectionStart, that.selectionEnd],
                            value: [that.selectionStart, that.selectionEnd]
                        };
                    } else {
                        slideParams = { value: that.val };
                    }

                    owner.trigger(SLIDE, slideParams);
                }

                that._updateTooltip(that.val);
            },

            _updateTooltip: function (val) {
                var that = this,
                options = that.options,
                tooltip = options.tooltip,
                html = "";

                if (!tooltip.enabled) {
                    return;
                }

                if (!that.tooltipDiv) {
                    that._createTooltip();
                }

                html = that.owner._getFormattedValue(round(val), that);

                if (!that.type) {
                    html += that.tooltipInnerDiv;
                }

                that.tooltipDiv.html(html);
                that.moveTooltip();
            },

            dragcancel: function () {
                this.owner._refresh();
                $(document.documentElement).css("cursor", "");
                return this._end();
            },

            dragend: function () {
                var that = this,
                owner = that.owner;

                $(document.documentElement).css("cursor", "");

                if (that.type) {
                    owner._update(that.selectionStart, that.selectionEnd);
                } else {
                    owner._update(that.val);
                    that.draggable.userEvents._disposeAll();
                }

                return that._end();
            },

            _end: function () {
                var that = this,
                owner = that.owner;

                owner._focusWithMouse(that.element);

                owner.element.on(MOUSE_OVER);

                return false;
            },

            _removeTooltip: function (noAnimation) {
                var that = this,
                owner = that.owner;

                if (that.tooltipDiv && owner.options.tooltip.enabled && owner.options.enabled) {
                    if (noAnimation) {
                        that.tooltipDiv.remove();
                        that.tooltipDiv = null;
                    } else {
                        that.tooltipDiv.fadeOut("slow", function () {
                            $(this).remove();
                            that.tooltipDiv = null;
                        });
                    }
                }
            },

            moveTooltip: function () {
                var that = this,
                owner = that.owner,
                top = 0,
                left = 0,
                element = that.element,
                offset = kendo.getOffset(element),
                margin = 8,
                viewport = $(window),
                callout = that.tooltipDiv.find(".k-callout"),
                width = that.tooltipDiv.outerWidth(),
                height = that.tooltipDiv.outerHeight(),
                dragHandles, sdhOffset, diff, anchorSize;

                if (that.type) {
                    dragHandles = owner.wrapper.find(DRAG_HANDLE);
                    offset = kendo.getOffset(dragHandles.eq(0));
                    sdhOffset = kendo.getOffset(dragHandles.eq(1));

                    if (owner._isHorizontal) {
                        top = sdhOffset.top;
                        left = offset.left + ((sdhOffset.left - offset.left) / 2);
                    } else {
                        top = offset.top + ((sdhOffset.top - offset.top) / 2);
                        left = sdhOffset.left;
                    }

                    anchorSize = dragHandles.eq(0).outerWidth() + 2 * margin;
                } else {
                    top = offset.top;
                    left = offset.left;
                    anchorSize = element.outerWidth() + 2 * margin;
                }

                if (owner._isHorizontal) {
                    left -= parseInt((width - element[owner._outerSize]()) / 2, 10);
                    top -= height + callout.height() + margin;
                } else {
                    top -= parseInt((height - element[owner._outerSize]()) / 2, 10);
                    left -= width + callout.width() + margin;
                }

                if (owner._isHorizontal) {
                    diff = that._flip(top, height, anchorSize, viewport.outerHeight() + that._scrollOffset.top);
                    top += diff;
                    left += that._fit(left, width, viewport.outerWidth() + that._scrollOffset.left);
                } else {
                    diff = that._flip(left, width, anchorSize, viewport.outerWidth() + that._scrollOffset.left);
                    top += that._fit(top, height, viewport.outerHeight() + that._scrollOffset.top);
                    left += diff;
                }

                if (diff > 0 && callout) {
                    callout.removeClass();
                    callout.addClass("k-callout k-callout-" + (owner._isHorizontal ? "n" : "w"));
                }

                that.tooltipDiv.css({ top: top, left: left });
            },

            _fit: function (position, size, viewPortEnd) {
                var output = 0;

                if (position + size > viewPortEnd) {
                    output = viewPortEnd - (position + size);
                }

                if (position < 0) {
                    output = -position;
                }

                return output;
            },

            _flip: function (offset, size, anchorSize, viewPortEnd) {
                var output = 0;

                if (offset + size > viewPortEnd) {
                    output += -(anchorSize + size);
                }

                if (offset + output < 0) {
                    output += anchorSize + size;
                }

                return output;
            },

            constrainValue: function (position, min, max, maxOverflow) {
                var that = this,
                val = 0;

                if (min < position && position < max) {
                    val = that.owner._getValueFromPosition(position, that.dragableArea);
                } else {
                    if (maxOverflow) {
                        val = that.options.max;
                    } else {
                        val = that.options.min;
                    }
                }

                return val;
            }

        };

        kendo.ui.plugin(Slider);

        var RangeSlider = SliderBase.extend({
            init: function (element, options) {
                var that = this,
                inputs = $(element).find("input"),
                firstInput = inputs.eq(0)[0],
                secondInput = inputs.eq(1)[0];

                firstInput.type = "text";
                secondInput.type = "text";

                options = extend({}, {
                    selectionStart: parseAttr(firstInput, "value"),
                    min: parseAttr(firstInput, "min"),
                    max: parseAttr(firstInput, "max"),
                    smallStep: parseAttr(firstInput, "step")
                }, {
                    selectionEnd: parseAttr(secondInput, "value"),
                    min: parseAttr(secondInput, "min"),
                    max: parseAttr(secondInput, "max"),
                    smallStep: parseAttr(secondInput, "step")
                }, options);

                if (options && options.enabled === undefined) {
                    options.enabled = !inputs.is("[disabled]");
                }

                SliderBase.fn.init.call(that, element, options);
                options = that.options;
                if (!defined(options.selectionStart) || options.selectionStart === null) {
                    options.selectionStart = options.min;
                    inputs.eq(0).prop("value", formatValue(options.min));
                }

                if (!defined(options.selectionEnd) || options.selectionEnd === null) {
                    options.selectionEnd = options.max;
                    inputs.eq(1).prop("value", formatValue(options.max));
                }

                var dragHandles = that.wrapper.find(DRAG_HANDLE);

                new RangeSlider.Selection(dragHandles, that, options);
                that._firstHandleDrag = new Slider.Drag(dragHandles.eq(0), "firstHandle", that, options);
                that._lastHandleDrag = new Slider.Drag(dragHandles.eq(1), "lastHandle", that, options);
            },

            options: {
                name: "RangeSlider",
                leftDragHandleTitle: "drag",
                rightDragHandleTitle: "drag",
                tooltip: { format: "{0:#,#.##}" },
                selectionStart: null,
                selectionEnd: null
            },

            enable: function (enable) {
                var that = this,
                options = that.options,
                clickHandler;

                that.disable();
                if (enable === false) {
                    return;
                }

                that.wrapper
                .removeClass(STATE_DISABLED)
                .addClass(STATE_DEFAULT);

                that.wrapper.find("input").removeAttr(DISABLED);

                clickHandler = function (e) {
                    var touch = getTouches(e)[0];

                    if (!touch) {
                        return;
                    }

                    var mousePosition = that._isHorizontal ? touch.location.pageX : touch.location.pageY,
                    dragableArea = that._getDraggableArea(),
                    val = that._getValueFromPosition(mousePosition, dragableArea),
                    target = $(e.target),
                    from, to, drag;

                    if (target.hasClass("k-draghandle")) {
                        that.wrapper.find("." + STATE_FOCUSED).removeClass(STATE_FOCUSED + " " + STATE_SELECTED);
                        target.addClass(STATE_FOCUSED + " " + STATE_SELECTED);
                        return;
                    }

                    if (val < options.selectionStart) {
                        from = val;
                        to = options.selectionEnd;
                        drag = that._firstHandleDrag;
                    } else if (val > that.selectionEnd) {
                        from = options.selectionStart;
                        to = val;
                        drag = that._lastHandleDrag;
                    } else {
                        if (val - options.selectionStart <= options.selectionEnd - val) {
                            from = val;
                            to = options.selectionEnd;
                            drag = that._firstHandleDrag;
                        } else {
                            from = options.selectionStart;
                            to = val;
                            drag = that._lastHandleDrag;
                        }
                    }

                    drag.dragstart(e);
                    that._setValueInRange(from, to);
                    that._focusWithMouse(drag.element);
                };

                that.wrapper
                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR)
                    .on(TRACK_MOUSE_DOWN, clickHandler)
                    .end()
                    .on(TRACK_MOUSE_DOWN, function () {
                        $(document.documentElement).one("selectstart", kendo.preventDefault);
                    })
                    .on(TRACK_MOUSE_UP, function () {
                        if (that._activeDragHandle) {
                            that._activeDragHandle._end();
                        }
                    });

                that.wrapper
                .find(DRAG_HANDLE)
                .attr(TABINDEX, 0)
                .on(MOUSE_UP, function () {
                    that._setTooltipTimeout();
                })
                .on(CLICK, function (e) {
                    that._focusWithMouse(e.target);
                    e.preventDefault();
                })
                .on(FOCUS, proxy(that._focus, that))
                .on(BLUR, proxy(that._blur, that));

                that.wrapper.find(DRAG_HANDLE)
                .off(KEY_DOWN, kendo.preventDefault)
                .eq(0).on(KEY_DOWN,
                    proxy(function (e) {
                        this._keydown(e, "firstHandle");
                    }, that)
                )
                .end()
                .eq(1).on(KEY_DOWN,
                    proxy(function (e) {
                        this._keydown(e, "lastHandle");
                    }, that)
                );

                that.options.enabled = true;
            },

            disable: function () {
                var that = this;

                that.wrapper
                .removeClass(STATE_DEFAULT)
                .addClass(STATE_DISABLED);

                that.wrapper.find("input").prop(DISABLED, DISABLED);

                that.wrapper
                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR).off(TRACK_MOUSE_DOWN).off(TRACK_MOUSE_UP);

                that.wrapper
                .find(DRAG_HANDLE)
                .attr(TABINDEX, -1)
                .off(MOUSE_UP)
                .off(KEY_DOWN)
                .off(CLICK)
                .off(FOCUS)
                .off(BLUR);

                that.options.enabled = false;
            },

            _keydown: function (e, handle) {
                var that = this,
                selectionStartValue = that.options.selectionStart,
                selectionEndValue = that.options.selectionEnd,
                dragSelectionStart,
                dragSelectionEnd,
                activeHandleDrag;

                if (e.keyCode in that._keyMap) {

                    that._clearTooltipTimeout();

                    if (handle == "firstHandle") {
                        activeHandleDrag = that._activeHandleDrag = that._firstHandleDrag;
                        selectionStartValue = that._keyMap[e.keyCode](selectionStartValue);

                        if (selectionStartValue > selectionEndValue) {
                            selectionEndValue = selectionStartValue;
                        }
                    } else {
                        activeHandleDrag = that._activeHandleDrag = that._lastHandleDrag;
                        selectionEndValue = that._keyMap[e.keyCode](selectionEndValue);

                        if (selectionStartValue > selectionEndValue) {
                            selectionStartValue = selectionEndValue;
                        }
                    }

                    that._setValueInRange(selectionStartValue, selectionEndValue);

                    dragSelectionStart = Math.max(selectionStartValue, that.options.selectionStart);
                    dragSelectionEnd = Math.min(selectionEndValue, that.options.selectionEnd);

                    activeHandleDrag.selectionEnd = Math.max(dragSelectionEnd, that.options.selectionStart);
                    activeHandleDrag.selectionStart = Math.min(dragSelectionStart, that.options.selectionEnd);

                    activeHandleDrag._updateTooltip(that.value()[that._activeHandle]);

                    e.preventDefault();
                }
            },

            _update: function (selectionStart, selectionEnd) {
                var that = this,
                values = that.value();

                var change = values[0] != selectionStart || values[1] != selectionEnd;

                that.value([selectionStart, selectionEnd]);

                if (change) {
                    that.trigger(CHANGE, {
                        values: [selectionStart, selectionEnd],
                        value: [selectionStart, selectionEnd]
                    });
                }
            },

            value: function (value) {
                if (value && value.length) {
                    return this._value(value[0], value[1]);
                } else {
                    return this._value();
                }
            },

            _value: function (start, end) {
                var that = this,
                options = that.options,
                selectionStart = options.selectionStart,
                selectionEnd = options.selectionEnd;

                if (isNaN(start) && isNaN(end)) {
                    return [selectionStart, selectionEnd];
                } else {
                    start = round(start);
                    end = round(end);
                }

                if (start >= options.min && start <= options.max &&
                end >= options.min && end <= options.max && start <= end) {
                    if (selectionStart != start || selectionEnd != end) {
                        that.element.find("input")
                        .eq(0).prop("value", formatValue(start))
                        .end()
                        .eq(1).prop("value", formatValue(end));

                        options.selectionStart = start;
                        options.selectionEnd = end;
                        that._refresh();
                        that._refreshAriaAttr(start, end);
                    }
                }
            },

            values: function (start, end) {
                if (isArray(start)) {
                    return this._value(start[0], start[1]);
                } else {
                    return this._value(start, end);
                }
            },

            _refresh: function () {
                var that = this,
                options = that.options;

                that.trigger(MOVE_SELECTION, {
                    values: [options.selectionStart, options.selectionEnd],
                    value: [options.selectionStart, options.selectionEnd]
                });

                if (options.selectionStart == options.max && options.selectionEnd == options.max) {
                    that._setZIndex("firstHandle");
                }
            },

            _refreshAriaAttr: function (start, end) {
                var that = this,
                dragHandles = that.wrapper.find(DRAG_HANDLE),
                drag = that._activeHandleDrag,
                formattedValue;

                formattedValue = that._getFormattedValue([start, end], drag);

                dragHandles.eq(0).attr("aria-valuenow", start);
                dragHandles.eq(1).attr("aria-valuenow", end);
                dragHandles.attr("aria-valuetext", formattedValue);
            },

            _setValueInRange: function (selectionStart, selectionEnd) {
                var options = this.options;

                selectionStart = math.max(math.min(selectionStart, options.max), options.min);

                selectionEnd = math.max(math.min(selectionEnd, options.max), options.min);

                if (selectionStart == options.max && selectionEnd == options.max) {
                    this._setZIndex("firstHandle");
                }

                this._update(math.min(selectionStart, selectionEnd), math.max(selectionStart, selectionEnd));
            },

            _setZIndex: function (type) {
                this.wrapper.find(DRAG_HANDLE).each(function (index) {
                    $(this).css("z-index", type == "firstHandle" ? 1 - index : index);
                });
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.wrapper.off(NS)
                .find(TICK_SELECTOR + ", " + TRACK_SELECTOR).off(NS)
                .end()
                .find(DRAG_HANDLE).off(NS);

                that._firstHandleDrag.draggable.destroy();
                that._lastHandleDrag.draggable.destroy();
            }
        });

        RangeSlider.Selection = function (dragHandles, that, options) {
            function moveSelection(value) {
                value = value || [];
                var selectionStartValue = value[0] - options.min,
                selectionEndValue = value[1] - options.min,
                selectionStartIndex = math.ceil(round(selectionStartValue / options.smallStep)),
                selectionEndIndex = math.ceil(round(selectionEndValue / options.smallStep)),
                selectionStart = that._pixelSteps[selectionStartIndex],
                selectionEnd = that._pixelSteps[selectionEndIndex],
                halfHandle = parseInt(dragHandles.eq(0)[that._outerSize]() / 2, 10),
                rtlCorrection = that._isRtl ? 2 : 0;

                dragHandles.eq(0).css(that._position, selectionStart - halfHandle - rtlCorrection)
                       .end()
                       .eq(1).css(that._position, selectionEnd - halfHandle - rtlCorrection);

                makeSelection(selectionStart, selectionEnd);
            }

            function makeSelection(selectionStart, selectionEnd) {
                var selection,
                selectionPosition,
                selectionDiv = that._trackDiv.find(".k-slider-selection");

                selection = math.abs(selectionStart - selectionEnd);

                selectionDiv[that._sizeFn](selection);
                if (that._isRtl) {
                    selectionPosition = math.max(selectionStart, selectionEnd);
                    selectionDiv.css("right", that._maxSelection - selectionPosition - 1);
                } else {
                    selectionPosition = math.min(selectionStart, selectionEnd);
                    selectionDiv.css(that._position, selectionPosition - 1);
                }
            }

            moveSelection(that.value());

            that.bind([CHANGE, SLIDE, MOVE_SELECTION], function (e) {
                moveSelection(e.values);
            });
        };

        kendo.ui.plugin(RangeSlider);

    })(window.kendo.jQuery);





    (function ($, parseInt, undefined) {
        // WARNING: removing the following jshint declaration and turning
        // == into === to make JSHint happy will break functionality.
        /*jshint eqnull:true  */
        var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        parseColor = kendo.parseColor,
        Color = kendo.Color,
        KEYS = kendo.keys,
        BACKGROUNDCOLOR = "background-color",
        ITEMSELECTEDCLASS = "k-state-selected",
        SIMPLEPALETTE = "000000,7f7f7f,880015,ed1c24,ff7f27,fff200,22b14c,00a2e8,3f48cc,a349a4,ffffff,c3c3c3,b97a57,ffaec9,ffc90e,efe4b0,b5e61d,99d9ea,7092be,c8bfe7",
        WEBPALETTE = "FFFFFF,FFCCFF,FF99FF,FF66FF,FF33FF,FF00FF,CCFFFF,CCCCFF,CC99FF,CC66FF,CC33FF,CC00FF,99FFFF,99CCFF,9999FF,9966FF,9933FF,9900FF,FFFFCC,FFCCCC,FF99CC,FF66CC,FF33CC,FF00CC,CCFFCC,CCCCCC,CC99CC,CC66CC,CC33CC,CC00CC,99FFCC,99CCCC,9999CC,9966CC,9933CC,9900CC,FFFF99,FFCC99,FF9999,FF6699,FF3399,FF0099,CCFF99,CCCC99,CC9999,CC6699,CC3399,CC0099,99FF99,99CC99,999999,996699,993399,990099,FFFF66,FFCC66,FF9966,FF6666,FF3366,FF0066,CCFF66,CCCC66,CC9966,CC6666,CC3366,CC0066,99FF66,99CC66,999966,996666,993366,990066,FFFF33,FFCC33,FF9933,FF6633,FF3333,FF0033,CCFF33,CCCC33,CC9933,CC6633,CC3333,CC0033,99FF33,99CC33,999933,996633,993333,990033,FFFF00,FFCC00,FF9900,FF6600,FF3300,FF0000,CCFF00,CCCC00,CC9900,CC6600,CC3300,CC0000,99FF00,99CC00,999900,996600,993300,990000,66FFFF,66CCFF,6699FF,6666FF,6633FF,6600FF,33FFFF,33CCFF,3399FF,3366FF,3333FF,3300FF,00FFFF,00CCFF,0099FF,0066FF,0033FF,0000FF,66FFCC,66CCCC,6699CC,6666CC,6633CC,6600CC,33FFCC,33CCCC,3399CC,3366CC,3333CC,3300CC,00FFCC,00CCCC,0099CC,0066CC,0033CC,0000CC,66FF99,66CC99,669999,666699,663399,660099,33FF99,33CC99,339999,336699,333399,330099,00FF99,00CC99,009999,006699,003399,000099,66FF66,66CC66,669966,666666,663366,660066,33FF66,33CC66,339966,336666,333366,330066,00FF66,00CC66,009966,006666,003366,000066,66FF33,66CC33,669933,666633,663333,660033,33FF33,33CC33,339933,336633,333333,330033,00FF33,00CC33,009933,006633,003333,000033,66FF00,66CC00,669900,666600,663300,660000,33FF00,33CC00,339900,336600,333300,330000,00FF00,00CC00,009900,006600,003300,000000",
        APPLY_CANCEL = {
            apply: "Apply",
            cancel: "Cancel"
        },
        NS = ".kendoColorTools",
        CLICK_NS = "click" + NS,
        KEYDOWN_NS = "keydown" + NS,

        browser = kendo.support.browser,
        isIE8 = browser.msie && browser.version < 9;

        var ColorSelector = Widget.extend({
            init: function (element, options) {
                var that = this, ariaId;

                Widget.fn.init.call(that, element, options);
                element = that.element;
                options = that.options;
                that._value = options.value = parseColor(options.value);
                that._tabIndex = element.attr("tabIndex") || 0;

                ariaId = that._ariaId = options.ariaId;
                if (ariaId) {
                    element.attr("aria-labelledby", ariaId);
                }

                if (options._standalone) {
                    that._triggerSelect = that._triggerChange;
                }
            },
            options: {
                name: "ColorSelector",
                value: null,
                _standalone: true
            },
            events: [
            "change",
            "select",
            "cancel"
        ],
            color: function (value) {
                if (value !== undefined) {
                    this._value = parseColor(value);
                    this._updateUI(this._value);
                }

                return this._value;
            },
            value: function (color) {
                color = this.color(color);

                if (color) {
                    if (this.options.opacity) {
                        color = color.toCssRgba();
                    } else {
                        color = color.toCss();
                    }
                }

                return color || null;
            },
            enable: function (enable) {
                if (arguments.length === 0) {
                    enable = true;
                }
                $(".k-disabled-overlay", this.wrapper).remove();
                if (!enable) {
                    this.wrapper.append("<div class='k-disabled-overlay'></div>");
                }
                this._onEnable(enable);
            },
            _select: function (color, nohooks) {
                var prev = this._value;
                color = this.color(color);
                if (!nohooks) {
                    this.element.trigger("change");
                    if (!color.equals(prev)) {
                        this.trigger("change", { value: this.value() });
                    } else if (!this._standalone) {
                        this.trigger("cancel");
                    }
                }
            },
            _triggerSelect: function (color) {
                triggerEvent(this, "select", color);
            },
            _triggerChange: function (color) {
                triggerEvent(this, "change", color);
            },
            destroy: function () {
                if (this.element) {
                    this.element.off(NS);
                }
                if (this.wrapper) {
                    this.wrapper.off(NS).find("*").off(NS);
                }
                this.wrapper = null;
                Widget.fn.destroy.call(this);
            },
            _updateUI: $.noop,
            _selectOnHide: function () {
                return null;
            },
            _cancel: function () {
                this.trigger("cancel");
            }
        });

        function triggerEvent(self, type, color) {
            color = parseColor(color);
            if (color && !color.equals(self.color())) {
                if (type == "change") {
                    // UI is already updated.  setting _value directly
                    // rather than calling self.color(color) to avoid an
                    // endless loop.
                    self._value = color;
                }
                if (color.a != 1) {
                    color = color.toCssRgba();
                } else {
                    color = color.toCss();
                }
                self.trigger(type, { value: color });
            }
        }

        var ColorPalette = ColorSelector.extend({
            init: function (element, options) {
                var that = this;
                ColorSelector.fn.init.call(that, element, options);
                element = that.wrapper = that.element;
                options = that.options;
                var colors = options.palette;

                if (colors == "websafe") {
                    colors = WEBPALETTE;
                    options.columns = 18;
                } else if (colors == "basic") {
                    colors = SIMPLEPALETTE;
                }

                if (typeof colors == "string") {
                    colors = colors.split(",");
                }

                if ($.isArray(colors)) {
                    colors = $.map(colors, function (x) { return parseColor(x); });
                }

                that._selectedID = (options.ariaId || kendo.guid()) + "_selected";

                element.addClass("k-widget k-colorpalette")
                .attr("role", "grid")
                .attr("aria-readonly", "true")
                .append($(that._template({
                    colors: colors,
                    columns: options.columns,
                    tileSize: options.tileSize,
                    value: that._value,
                    id: options.ariaId
                })))
                .on(CLICK_NS, ".k-item", function (ev) {
                    that._select($(ev.currentTarget).css(BACKGROUNDCOLOR));
                })
                .attr("tabIndex", that._tabIndex)
                .on(KEYDOWN_NS, bind(that._keydown, that));

                var tileSize = options.tileSize, width, height;
                if (tileSize) {
                    if (/number|string/.test(typeof tileSize)) {
                        width = height = parseFloat(tileSize);
                    } else if (typeof tileSize == "object") {
                        width = parseFloat(tileSize.width);
                        height = parseFloat(tileSize.height);
                    } else {
                        throw new Error("Unsupported value for the 'tileSize' argument");
                    }
                    element.find(".k-item").css({ width: width, height: height });
                }
            },
            focus: function () {
                this.wrapper.focus();
            },
            options: {
                name: "ColorPalette",
                columns: 10,
                tileSize: null,
                palette: "basic"
            },
            _onEnable: function (enable) {
                if (enable) {
                    this.wrapper.attr("tabIndex", this._tabIndex);
                } else {
                    this.wrapper.removeAttr("tabIndex");
                }
            },
            _keydown: function (e) {
                var selected,
                wrapper = this.wrapper,
                items = wrapper.find(".k-item"),
                current = items.filter("." + ITEMSELECTEDCLASS).get(0),
                keyCode = e.keyCode;

                if (keyCode == KEYS.LEFT) {
                    selected = relative(items, current, -1);
                } else if (keyCode == KEYS.RIGHT) {
                    selected = relative(items, current, 1);
                } else if (keyCode == KEYS.DOWN) {
                    selected = relative(items, current, this.options.columns);
                } else if (keyCode == KEYS.UP) {
                    selected = relative(items, current, -this.options.columns);
                } else if (keyCode == KEYS.ENTER) {
                    preventDefault(e);
                    if (current) {
                        this._select($(current).css(BACKGROUNDCOLOR));
                    }
                } else if (keyCode == KEYS.ESC) {
                    this._cancel();
                }

                if (selected) {
                    preventDefault(e);

                    this._current(selected);

                    try {
                        var color = parseColor(selected.css(BACKGROUNDCOLOR));
                        this._triggerSelect(color);
                    } catch (ex) { }
                }
            },
            _current: function (item) {
                this.wrapper.find("." + ITEMSELECTEDCLASS)
                .removeClass(ITEMSELECTEDCLASS)
                .attr("aria-selected", false)
                .removeAttr("id");

                $(item)
                .addClass(ITEMSELECTEDCLASS)
                .attr("aria-selected", true)
                .attr("id", this._selectedID);

                this.element
                .removeAttr("aria-activedescendant")
                .attr("aria-activedescendant", this._selectedID);
            },
            _updateUI: function (color) {
                var item = null;

                this.wrapper.find(".k-item").each(function () {
                    var c = parseColor($(this).css(BACKGROUNDCOLOR));

                    if (c && c.equals(color)) {
                        item = this;

                        return false;
                    }
                });

                this._current(item);
            },
            _template: kendo.template(
            '<table class="k-palette k-reset" role="presentation"><tr role="row">' +
              '# for (var i = 0; i < colors.length; ++i) { #' +
                '# var selected = colors[i].equals(value); #' +
                '# if (i && i % columns == 0) { # </tr><tr role="row"> # } #' +
                '<td role="gridcell" unselectable="on" style="background-color:#= colors[i].toCss() #"' +
                    '#= selected ? " aria-selected=true" : "" # ' +
                    '#=(id && i === 0) ? "id=\\""+id+"\\" " : "" # ' +
                    'class="k-item#= selected ? " ' + ITEMSELECTEDCLASS + '" : "" #" ' +
                    'aria-label="#= colors[i].toCss() #"></td>' +
              '# } #' +
            '</tr></table>'
        )
        });

        var FlatColorPicker = ColorSelector.extend({
            init: function (element, options) {
                var that = this;
                ColorSelector.fn.init.call(that, element, options);
                options = that.options;
                element = that.element;

                that.wrapper = element.addClass("k-widget k-flatcolorpicker")
                .append(that._template(options));

                that._hueElements = $(".k-hsv-rectangle, .k-transparency-slider .k-slider-track", element);

                that._selectedColor = $(".k-selected-color-display", element);

                that._colorAsText = $("input.k-color-value", element);

                that._sliders();

                that._hsvArea();

                that._updateUI(that._value || parseColor("#f00"));

                element
                .find("input.k-color-value").on(KEYDOWN_NS, function (ev) {
                    var input = this;
                    if (ev.keyCode == KEYS.ENTER) {
                        try {
                            var color = parseColor(input.value);
                            var val = that.color();
                            that._select(color, color.equals(val));
                        } catch (ex) {
                            $(input).addClass("k-state-error");
                        }
                    } else if (that.options.autoupdate) {
                        setTimeout(function () {
                            var color = parseColor(input.value, true);
                            if (color) {
                                that._updateUI(color, true);
                            }
                        }, 10);
                    }
                }).end()

                .on(CLICK_NS, ".k-controls button.apply", function () {
                    // calling select for the currently displayed
                    // color will trigger the "change" event.
                    that._select(that._getHSV());
                })
                .on(CLICK_NS, ".k-controls button.cancel", function () {
                    // but on cancel, we simply select the previous
                    // value (again, triggers "change" event).
                    that._updateUI(that.color());
                    that._cancel();
                });

                if (isIE8) {
                    // IE filters require absolute URLs
                    that._applyIEFilter();
                }
            },
            destroy: function () {
                this._hueSlider.destroy();
                if (this._opacitySlider) {
                    this._opacitySlider.destroy();
                }
                this._hueSlider = this._opacitySlider = this._hsvRect = this._hsvHandle =
                this._hueElements = this._selectedColor = this._colorAsText = null;
                ColorSelector.fn.destroy.call(this);
            },
            options: {
                name: "FlatColorPicker",
                opacity: false,
                buttons: false,
                input: true,
                preview: true,
                autoupdate: true,
                messages: APPLY_CANCEL
            },
            _applyIEFilter: function () {
                var track = this.element.find(".k-hue-slider .k-slider-track")[0],
                url = track.currentStyle.backgroundImage;

                url = url.replace(/^url\([\'\"]?|[\'\"]?\)$/g, "");
                track.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url + "', sizingMethod='scale')";
            },
            _sliders: function () {
                var that = this,
                element = that.element;

                function hueChange(e) {
                    that._updateUI(that._getHSV(e.value, null, null, null));
                }

                that._hueSlider = element.find(".k-hue-slider").kendoSlider({
                    min: 0,
                    max: 359,
                    tickPlacement: "none",
                    showButtons: false,
                    slide: hueChange,
                    change: hueChange
                }).data("kendoSlider");

                function opacityChange(e) {
                    that._updateUI(that._getHSV(null, null, null, e.value / 100));
                }

                that._opacitySlider = element.find(".k-transparency-slider").kendoSlider({
                    min: 0,
                    max: 100,
                    tickPlacement: "none",
                    showButtons: false,
                    slide: opacityChange,
                    change: opacityChange
                }).data("kendoSlider");
            },
            _hsvArea: function () {
                var that = this,
                element = that.element,
                hsvRect = element.find(".k-hsv-rectangle"),
                hsvHandle = hsvRect.find(".k-draghandle").attr("tabIndex", 0).on(KEYDOWN_NS, bind(that._keydown, that));

                function update(x, y) {
                    var offset = this.offset,
                    dx = x - offset.left, dy = y - offset.top,
                    rw = this.width, rh = this.height;

                    dx = dx < 0 ? 0 : dx > rw ? rw : dx;
                    dy = dy < 0 ? 0 : dy > rh ? rh : dy;
                    that._svChange(dx / rw, 1 - dy / rh);
                }

                that._hsvEvents = new kendo.UserEvents(hsvRect, {
                    global: true,
                    press: function (e) {
                        this.offset = kendo.getOffset(hsvRect);
                        this.width = hsvRect.width();
                        this.height = hsvRect.height();
                        hsvHandle.focus();
                        update.call(this, e.x.location, e.y.location);
                    },
                    start: function () {
                        hsvRect.addClass("k-dragging");
                        hsvHandle.focus();
                    },
                    move: function (e) {
                        e.preventDefault();
                        update.call(this, e.x.location, e.y.location);
                    },
                    end: function () {
                        hsvRect.removeClass("k-dragging");
                    }
                });

                that._hsvRect = hsvRect;
                that._hsvHandle = hsvHandle;
            },
            _onEnable: function (enable) {
                this._hueSlider.enable(enable);

                if (this._opacitySlider) {
                    this._opacitySlider.enable(enable);
                }

                this.wrapper.find("input").attr("disabled", !enable);

                var handle = this._hsvRect.find(".k-draghandle");

                if (enable) {
                    handle.attr("tabIndex", this._tabIndex);
                } else {
                    handle.removeAttr("tabIndex");
                }
            },
            _keydown: function (ev) {
                var that = this;
                function move(prop, d) {
                    var c = that._getHSV();
                    c[prop] += d * (ev.shiftKey ? 0.01 : 0.05);
                    if (c[prop] < 0) { c[prop] = 0; }
                    if (c[prop] > 1) { c[prop] = 1; }
                    that._updateUI(c);
                    preventDefault(ev);
                }
                function hue(d) {
                    var c = that._getHSV();
                    c.h += d * (ev.shiftKey ? 1 : 5);
                    if (c.h < 0) { c.h = 0; }
                    if (c.h > 359) { c.h = 359; }
                    that._updateUI(c);
                    preventDefault(ev);
                }
                switch (ev.keyCode) {
                    case KEYS.LEFT:
                        if (ev.ctrlKey) {
                            hue(-1);
                        } else {
                            move("s", -1);
                        }
                        break;
                    case KEYS.RIGHT:
                        if (ev.ctrlKey) {
                            hue(1);
                        } else {
                            move("s", 1);
                        }
                        break;
                    case KEYS.UP:
                        move(ev.ctrlKey && that._opacitySlider ? "a" : "v", 1);
                        break;
                    case KEYS.DOWN:
                        move(ev.ctrlKey && that._opacitySlider ? "a" : "v", -1);
                        break;
                    case KEYS.ENTER:
                        that._select(that._getHSV());
                        break;
                    case KEYS.F2:
                        that.wrapper.find("input.k-color-value").focus().select();
                        break;
                    case KEYS.ESC:
                        that._cancel();
                        break;
                }
            },
            focus: function () {
                this._hsvHandle.focus();
            },
            _getHSV: function (h, s, v, a) {
                var rect = this._hsvRect,
                width = rect.width(),
                height = rect.height(),
                handlePosition = this._hsvHandle.position();

                if (h == null) {
                    h = this._hueSlider.value();
                }
                if (s == null) {
                    s = handlePosition.left / width;
                }
                if (v == null) {
                    v = 1 - handlePosition.top / height;
                }
                if (a == null) {
                    a = this._opacitySlider ? this._opacitySlider.value() / 100 : 1;
                }
                return Color.fromHSV(h, s, v, a);
            },
            _svChange: function (s, v) {
                var color = this._getHSV(null, s, v, null);
                this._updateUI(color);
            },
            _updateUI: function (color, dontChangeInput) {
                var that = this,
                rect = that._hsvRect;

                if (!color) {
                    return;
                }

                this._colorAsText.removeClass("k-state-error");

                that._selectedColor.css(BACKGROUNDCOLOR, color.toDisplay());
                if (!dontChangeInput) {
                    that._colorAsText.val(that._opacitySlider ? color.toCssRgba() : color.toCss());
                }
                that._triggerSelect(color);

                color = color.toHSV();
                that._hsvHandle.css({
                    // saturation is 0 on the left side, full (1) on the right
                    left: color.s * rect.width() + "px",
                    // value is 0 on the bottom, full on the top.
                    top: (1 - color.v) * rect.height() + "px"
                });

                that._hueElements.css(BACKGROUNDCOLOR, Color.fromHSV(color.h, 1, 1, 1).toCss());
                that._hueSlider.value(color.h);

                if (that._opacitySlider) {
                    that._opacitySlider.value(100 * color.a);
                }
            },
            _selectOnHide: function () {
                return this.options.buttons ? null : this._getHSV();
            },
            _template: kendo.template(
            '# if (preview) { #' +
                '<div class="k-selected-color"><div class="k-selected-color-display"><input class="k-color-value" #= !data.input ? \'style=\"visibility: hidden;\"\' : \"\" #></div></div>' +
            '# } #' +
            '<div class="k-hsv-rectangle"><div class="k-hsv-gradient"></div><div class="k-draghandle"></div></div>' +
            '<input class="k-hue-slider" />' +
            '# if (opacity) { #' +
                '<input class="k-transparency-slider" />' +
            '# } #' +
            '# if (buttons) { #' +
                '<div unselectable="on" class="k-controls"><button class="k-button k-primary apply">#: messages.apply #</button> <button class="k-button cancel">#: messages.cancel #</button></div>' +
            '# } #'
        )
        });

        function relative(array, element, delta) {
            array = Array.prototype.slice.call(array);
            var n = array.length;
            var pos = array.indexOf(element);
            if (pos < 0) {
                return delta < 0 ? array[n - 1] : array[0];
            }
            pos += delta;
            if (pos < 0) {
                pos += n;
            } else {
                pos %= n;
            }
            return array[pos];
        }

        /* -----[ The ColorPicker widget ]----- */

        var ColorPicker = Widget.extend({
            init: function (element, options) {
                var that = this;
                Widget.fn.init.call(that, element, options);
                options = that.options;
                element = that.element;

                var value = element.attr("value") || element.val();
                if (value) {
                    value = parseColor(value, true);
                } else {
                    value = parseColor(options.value, true);
                }
                that._value = options.value = value;

                var content = that.wrapper = $(that._template(options));
                element.hide().after(content);

                if (element.is("input")) {
                    element.appendTo(content);

                    // if there exists a <label> associated with this
                    // input field, we must catch clicks on it to prevent
                    // the built-in color picker from showing up.
                    // https://github.com/telerik/kendo-ui-core/issues/292

                    var label = element.closest("label");
                    var id = element.attr("id");
                    if (id) {
                        label = label.add('label[for="' + id + '"]');
                    }
                    label.click(function (ev) {
                        that.open();
                        ev.preventDefault();
                    });
                }

                that._tabIndex = element.attr("tabIndex") || 0;

                that.enable(!element.attr("disabled"));

                var accesskey = element.attr("accesskey");
                if (accesskey) {
                    element.attr("accesskey", null);
                    content.attr("accesskey", accesskey);
                }

                that.bind("activate", function (ev) {
                    if (!ev.isDefaultPrevented()) {
                        that.toggle();
                    }
                });

                that._updateUI(value);
            },
            destroy: function () {
                this.wrapper.off(NS).find("*").off(NS);
                if (this._popup) {
                    this._selector.destroy();
                    this._popup.destroy();
                }
                this._selector = this._popup = this.wrapper = null;
                Widget.fn.destroy.call(this);
            },
            enable: function (enable) {
                var that = this,
                wrapper = that.wrapper,
                innerWrapper = wrapper.children(".k-picker-wrap"),
                icon = innerWrapper.find(".k-select");

                if (arguments.length === 0) {
                    enable = true;
                }

                that.element.attr("disabled", !enable);
                wrapper.attr("aria-disabled", !enable);

                icon.off(NS).on("mousedown" + NS, preventDefault);

                wrapper.addClass("k-state-disabled")
                .removeAttr("tabIndex")
                .add("*", wrapper).off(NS);

                if (enable) {
                    wrapper.removeClass("k-state-disabled")
                    .attr("tabIndex", that._tabIndex)
                    .on("mouseenter" + NS, function () { innerWrapper.addClass("k-state-hover"); })
                    .on("mouseleave" + NS, function () { innerWrapper.removeClass("k-state-hover"); })
                    .on("focus" + NS, function () { innerWrapper.addClass("k-state-focused"); })
                    .on("blur" + NS, function () { innerWrapper.removeClass("k-state-focused"); })
                    .on(KEYDOWN_NS, bind(that._keydown, that))
                    .on(CLICK_NS, ".k-icon", bind(that.toggle, that))
                    .on(CLICK_NS, that.options.toolIcon ? ".k-tool-icon" : ".k-selected-color", function () {
                        that.trigger("activate");
                    });
                }
            },

            _template: kendo.template(
            '<span role="textbox" aria-haspopup="true" class="k-widget k-colorpicker k-header">' +
                '<span class="k-picker-wrap k-state-default">' +
                    '# if (toolIcon) { #' +
                        '<span class="k-tool-icon #= toolIcon #">' +
                            '<span class="k-selected-color"></span>' +
                        '</span>' +
                    '# } else { #' +
                        '<span class="k-selected-color"></span>' +
                    '# } #' +
                    '<span class="k-select" unselectable="on">' +
                        '<span class="k-icon k-i-arrow-s" unselectable="on"></span>' +
                    '</span>' +
                '</span>' +
            '</span>'
        ),

            options: {
                name: "ColorPicker",
                palette: null,
                columns: 10,
                toolIcon: null,
                value: null,
                messages: APPLY_CANCEL,
                opacity: false,
                buttons: true,
                preview: true,
                ARIATemplate: 'Current selected color is #=data || ""#'
            },

            events: ["activate", "change", "select", "open", "close"],

            open: function () {
                this._getPopup().open();
            },
            close: function () {
                this._getPopup().close();
            },
            toggle: function () {
                this._getPopup().toggle();
            },
            color: ColorSelector.fn.color,
            value: ColorSelector.fn.value,
            _select: ColorSelector.fn._select,
            _triggerSelect: ColorSelector.fn._triggerSelect,
            _isInputTypeColor: function () {
                var el = this.element[0];
                return (/^input$/i).test(el.tagName) && (/^color$/i).test(el.type);
            },

            _updateUI: function (value) {
                var formattedValue = "";

                if (value) {
                    if (this._isInputTypeColor() || value.a == 1) {
                        // seems that input type="color" doesn't support opacity
                        // in colors; the only accepted format is hex #RRGGBB
                        formattedValue = value.toCss();
                    } else {
                        formattedValue = value.toCssRgba();
                    }

                    this.element.val(formattedValue);
                }

                if (!this._ariaTemplate) {
                    this._ariaTemplate = kendo.template(this.options.ARIATemplate);
                }

                this.wrapper.attr("aria-label", this._ariaTemplate(formattedValue));

                this._triggerSelect(value);
                this.wrapper.find(".k-selected-color").css(
                BACKGROUNDCOLOR,
                value ? value.toDisplay() : "transparent"
            );
            },
            _keydown: function (ev) {
                var key = ev.keyCode;
                if (this._getPopup().visible()) {
                    if (key == KEYS.ESC) {
                        this._selector._cancel();
                    } else {
                        this._selector._keydown(ev);
                    }
                    preventDefault(ev);
                }
                else if (key == KEYS.ENTER || key == KEYS.DOWN) {
                    this.open();
                    preventDefault(ev);
                }
            },
            _getPopup: function () {
                var that = this, popup = that._popup;

                if (!popup) {
                    var options = that.options;
                    var selectorType;

                    if (options.palette) {
                        selectorType = ColorPalette;
                    } else {
                        selectorType = FlatColorPicker;
                    }

                    options._standalone = false;
                    delete options.select;
                    delete options.change;
                    delete options.cancel;

                    var id = kendo.guid();
                    var selector = that._selector = new selectorType($('<div id="' + id + '"/>').appendTo(document.body), options);

                    that.wrapper.attr("aria-owns", id);

                    that._popup = popup = selector.wrapper.kendoPopup({
                        anchor: that.wrapper
                    }).data("kendoPopup");

                    selector.bind({
                        select: function (ev) {
                            that._updateUI(parseColor(ev.value));
                        },
                        change: function () {
                            that._select(selector.color());
                            that.close();
                        },
                        cancel: function () {
                            that.close();
                        }
                    });
                    popup.bind({
                        close: function (ev) {
                            if (that.trigger("close")) {
                                ev.preventDefault();
                                return;
                            }
                            that.wrapper.children(".k-picker-wrap").removeClass("k-state-focused");
                            var color = selector._selectOnHide();
                            if (!color) {
                                that.wrapper.focus();
                                that._updateUI(that.color());
                            } else {
                                that._select(color);
                            }
                        },
                        open: function (ev) {
                            if (that.trigger("open")) {
                                ev.preventDefault();
                            } else {
                                that.wrapper.children(".k-picker-wrap").addClass("k-state-focused");
                            }
                        },
                        activate: function () {
                            selector._select(that.color(), true);
                            selector.focus();
                            that.wrapper.children(".k-picker-wrap").addClass("k-state-focused");
                        }
                    });
                }
                return popup;
            }
        });

        function preventDefault(ev) { ev.preventDefault(); }

        function bind(callback, obj) {
            return function () {
                return callback.apply(obj, arguments);
            };
        }

        ui.plugin(ColorPalette);
        ui.plugin(FlatColorPicker);
        ui.plugin(ColorPicker);

    })(jQuery, parseInt);





    (function ($, undefined) {
        var kendo = window.kendo,
        caret = kendo.caret,
        keys = kendo.keys,
        ui = kendo.ui,
        Widget = ui.Widget,
        activeElement = kendo._activeElement,
        extractFormat = kendo._extractFormat,
        parse = kendo.parseFloat,
        placeholderSupported = kendo.support.placeholder,
        getCulture = kendo.getCulture,
        round = kendo._round,
        CHANGE = "change",
        DISABLED = "disabled",
        READONLY = "readonly",
        INPUT = "k-input",
        SPIN = "spin",
        ns = ".kendoNumericTextBox",
        TOUCHEND = "touchend",
        MOUSELEAVE = "mouseleave" + ns,
        HOVEREVENTS = "mouseenter" + ns + " " + MOUSELEAVE,
        DEFAULT = "k-state-default",
        FOCUSED = "k-state-focused",
        HOVER = "k-state-hover",
        FOCUS = "focus",
        POINT = ".",
        SELECTED = "k-state-selected",
        STATEDISABLED = "k-state-disabled",
        ARIA_DISABLED = "aria-disabled",
        ARIA_READONLY = "aria-readonly",
        INTEGER_REGEXP = /^(-)?(\d*)$/,
        NULL = null,
        proxy = $.proxy;

        var NumericTextBox = Widget.extend({
            init: function (element, options) {
                var that = this,
             isStep = options && options.step !== undefined,
             min, max, step, value, disabled;

                Widget.fn.init.call(that, element, options);

                options = that.options;
                element = that.element
                           .on("focusout" + ns, proxy(that._focusout, that))
                           .attr("role", "spinbutton");

                options.placeholder = options.placeholder || element.attr("placeholder");

                that._reset();
                that._wrapper();
                that._arrows();
                that._input();

                if (!kendo.support.mobileOS) {
                    that._text.on(FOCUS + ns, proxy(that._click, that));
                } else {
                    that._text.on(TOUCHEND + ns + " " + FOCUS + ns, function (e) {
                        that._toggleText(false);
                        if (e.type === FOCUS) {
                            element.focus();
                        }
                    });
                }

                min = that.min(element.attr("min"));
                max = that.max(element.attr("max"));
                step = that._parse(element.attr("step"));

                if (options.min === NULL && min !== NULL) {
                    options.min = min;
                }

                if (options.max === NULL && max !== NULL) {
                    options.max = max;
                }

                if (!isStep && step !== NULL) {
                    options.step = step;
                }

                element.attr("aria-valuemin", options.min)
                    .attr("aria-valuemax", options.max);

                options.format = extractFormat(options.format);

                value = options.value;
                that.value(value !== NULL ? value : element.val());

                disabled = element.is("[disabled]");
                if (disabled) {
                    that.enable(false);
                } else {
                    that.readonly(element.is("[readonly]"));
                }

                kendo.notify(that);
            },

            options: {
                name: "NumericTextBox",
                decimals: NULL,
                min: NULL,
                max: NULL,
                value: NULL,
                step: 1,
                culture: "",
                format: "n",
                spinners: true,
                placeholder: "",
                upArrowText: "Increase value",
                downArrowText: "Decrease value"
            },
            events: [
            CHANGE,
            SPIN
        ],

            _editable: function (options) {
                var that = this,
                element = that.element,
                disable = options.disable,
                readonly = options.readonly,
                text = that._text.add(element),
                wrapper = that._inputWrapper.off(HOVEREVENTS);

                that._toggleText(true);

                that._upArrowEventHandler.unbind("press");
                that._downArrowEventHandler.unbind("press");
                element.off("keydown" + ns).off("keypress" + ns).off("paste" + ns);

                if (!readonly && !disable) {
                    wrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED)
                    .on(HOVEREVENTS, that._toggleHover);

                    text.removeAttr(DISABLED)
                    .removeAttr(READONLY)
                    .attr(ARIA_DISABLED, false)
                    .attr(ARIA_READONLY, false);

                    that._upArrowEventHandler.bind("press", function (e) {
                        e.preventDefault();
                        that._spin(1);
                        that._upArrow.addClass(SELECTED);
                    });

                    that._downArrowEventHandler.bind("press", function (e) {
                        e.preventDefault();
                        that._spin(-1);
                        that._downArrow.addClass(SELECTED);
                    });

                    that.element
                    .on("keydown" + ns, proxy(that._keydown, that))
                    .on("keypress" + ns, proxy(that._keypress, that))
                    .on("paste" + ns, proxy(that._paste, that));

                } else {
                    wrapper
                    .addClass(disable ? STATEDISABLED : DEFAULT)
                    .removeClass(disable ? DEFAULT : STATEDISABLED);

                    text.attr(DISABLED, disable)
                    .attr(READONLY, readonly)
                    .attr(ARIA_DISABLED, disable)
                    .attr(ARIA_READONLY, readonly);
                }
            },

            readonly: function (readonly) {
                this._editable({
                    readonly: readonly === undefined ? true : readonly,
                    disable: false
                });
            },

            enable: function (enable) {
                this._editable({
                    readonly: false,
                    disable: !(enable = enable === undefined ? true : enable)
                });
            },

            destroy: function () {
                var that = this;

                that.element
                .add(that._text)
                .add(that._upArrow)
                .add(that._downArrow)
                .add(that._inputWrapper)
                .off(ns);

                that._upArrowEventHandler.destroy();
                that._downArrowEventHandler.destroy();

                if (that._form) {
                    that._form.off("reset", that._resetHandler);
                }

                Widget.fn.destroy.call(that);
            },

            min: function (value) {
                return this._option("min", value);
            },

            max: function (value) {
                return this._option("max", value);
            },

            step: function (value) {
                return this._option("step", value);
            },

            value: function (value) {
                var that = this, adjusted;

                if (value === undefined) {
                    return that._value;
                }

                value = that._parse(value);
                adjusted = that._adjust(value);

                if (value !== adjusted) {
                    return;
                }

                that._update(value);
                that._old = that._value;
            },

            focus: function () {
                this._focusin();
            },

            _adjust: function (value) {
                var that = this,
            options = that.options,
            min = options.min,
            max = options.max;

                if (value === NULL) {
                    return value;
                }

                if (min !== NULL && value < min) {
                    value = min;
                } else if (max !== NULL && value > max) {
                    value = max;
                }

                return value;
            },

            _arrows: function () {
                var that = this,
            arrows,
            _release = function () {
                clearTimeout(that._spinning);
                arrows.removeClass(SELECTED);
            },
            options = that.options,
            spinners = options.spinners,
            element = that.element;

                arrows = element.siblings(".k-icon");

                if (!arrows[0]) {
                    arrows = $(buttonHtml("n", options.upArrowText) + buttonHtml("s", options.downArrowText))
                        .insertAfter(element);

                    arrows.wrapAll('<span class="k-select"/>');
                }

                if (!spinners) {
                    arrows.parent().toggle(spinners);
                    that._inputWrapper.addClass("k-expand-padding");
                }

                that._upArrow = arrows.eq(0);
                that._upArrowEventHandler = new kendo.UserEvents(that._upArrow, { release: _release });
                that._downArrow = arrows.eq(1);
                that._downArrowEventHandler = new kendo.UserEvents(that._downArrow, { release: _release });
            },

            _blur: function () {
                var that = this;

                that._toggleText(true);
                that._change(that.element.val());
            },

            _click: function (e) {
                var that = this;

                clearTimeout(that._focusing);
                that._focusing = setTimeout(function () {
                    var input = e.target,
                    idx = caret(input)[0],
                    value = input.value.substring(0, idx),
                    format = that._format(that.options.format),
                    group = format[","],
                    result, groupRegExp, extractRegExp,
                    caretPosition = 0;

                    if (group) {
                        groupRegExp = new RegExp("\\" + group, "g");
                        extractRegExp = new RegExp("([\\d\\" + group + "]+)(\\" + format[POINT] + ")?(\\d+)?");
                    }

                    if (extractRegExp) {
                        result = extractRegExp.exec(value);
                    }

                    if (result) {
                        caretPosition = result[0].replace(groupRegExp, "").length;

                        if (value.indexOf("(") != -1 && that._value < 0) {
                            caretPosition++;
                        }
                    }

                    that._focusin();

                    caret(that.element[0], caretPosition);
                });
            },

            _change: function (value) {
                var that = this;

                that._update(value);
                value = that._value;

                if (that._old != value) {
                    that._old = value;

                    // trigger the DOM change event so any subscriber gets notified
                    that.element.trigger(CHANGE);

                    that.trigger(CHANGE);
                }
            },

            _culture: function (culture) {
                return culture || getCulture(this.options.culture);
            },

            _focusin: function () {
                var that = this;
                that._inputWrapper.addClass(FOCUSED);
                that._toggleText(false);
                that.element[0].focus();
            },

            _focusout: function () {
                var that = this;

                clearTimeout(that._focusing);
                that._inputWrapper.removeClass(FOCUSED).removeClass(HOVER);
                that._blur();
            },

            _format: function (format, culture) {
                var numberFormat = this._culture(culture).numberFormat;

                format = format.toLowerCase();

                if (format.indexOf("c") > -1) {
                    numberFormat = numberFormat.currency;
                } else if (format.indexOf("p") > -1) {
                    numberFormat = numberFormat.percent;
                }

                return numberFormat;
            },

            _input: function () {
                var that = this,
                CLASSNAME = "k-formatted-value",
                element = that.element.addClass(INPUT).show()[0],
                accessKey = element.accessKey,
                wrapper = that.wrapper,
                text;

                text = wrapper.find(POINT + CLASSNAME);

                if (!text[0]) {
                    text = $('<input type="text"/>').insertBefore(element).addClass(CLASSNAME);
                }

                try {
                    element.setAttribute("type", "text");
                } catch (e) {
                    element.type = "text";
                }

                text[0].tabIndex = element.tabIndex;
                text[0].style.cssText = element.style.cssText;
                text.prop("placeholder", that.options.placeholder);

                if (accessKey) {
                    text.attr("accesskey", accessKey);
                    element.accessKey = "";
                }

                that._text = text.addClass(element.className);
            },

            _keydown: function (e) {
                var that = this,
                key = e.keyCode;

                that._key = key;

                if (key == keys.DOWN) {
                    that._step(-1);
                } else if (key == keys.UP) {
                    that._step(1);
                } else if (key == keys.ENTER) {
                    that._change(that.element.val());
                }
            },

            _keypress: function (e) {
                if (e.which === 0 || e.ctrlKey || e.keyCode === keys.BACKSPACE || e.keyCode === keys.ENTER) {
                    return;
                }

                var that = this;
                var min = that.options.min;
                var element = that.element;
                var selection = caret(element);
                var selectionStart = selection[0];
                var selectionEnd = selection[1];
                var character = String.fromCharCode(e.which);
                var numberFormat = that._format(that.options.format);
                var isNumPadDecimal = that._key === keys.NUMPAD_DOT;
                var value = element.val();
                var isValid;

                if (isNumPadDecimal) {
                    character = numberFormat[POINT];
                }

                value = value.substring(0, selectionStart) + character + value.substring(selectionEnd);
                isValid = that._numericRegex(numberFormat).test(value);

                if (isValid && isNumPadDecimal) {
                    element.val(value);
                    caret(element, selectionStart + character.length);

                    e.preventDefault();
                } else if ((min !== null && min >= 0 && value.charAt(0) === "-") || !isValid) {
                    e.preventDefault();
                }

                that._key = 0;
            },

            _numericRegex: function (numberFormat) {
                var that = this;
                var separator = numberFormat[POINT];
                var precision = that.options.decimals;

                if (separator === POINT) {
                    separator = "\\" + separator;
                }

                if (precision === NULL) {
                    precision = numberFormat.decimals;
                }

                if (precision === 0) {
                    return INTEGER_REGEXP;
                }

                if (that._separator !== separator) {
                    that._separator = separator;
                    that._floatRegExp = new RegExp("^(-)?(((\\d+(" + separator + "\\d*)?)|(" + separator + "\\d*)))?$");
                }

                return that._floatRegExp;
            },

            _paste: function (e) {
                var that = this,
                element = e.target,
                value = element.value;

                setTimeout(function () {
                    if (that._parse(element.value) === NULL) {
                        that._update(value);
                    }
                });
            },

            _option: function (option, value) {
                var that = this,
                options = that.options;

                if (value === undefined) {
                    return options[option];
                }

                value = that._parse(value);

                if (!value && option === "step") {
                    return;
                }

                options[option] = value;
                that.element
                .attr("aria-value" + option, value)
                .attr(option, value);
            },

            _spin: function (step, timeout) {
                var that = this;

                timeout = timeout || 500;

                clearTimeout(that._spinning);
                that._spinning = setTimeout(function () {
                    that._spin(step, 50);
                }, timeout);

                that._step(step);
            },

            _step: function (step) {
                var that = this,
                element = that.element,
                value = that._parse(element.val()) || 0;

                if (activeElement() != element[0]) {
                    that._focusin();
                }

                value += that.options.step * step;

                that._update(that._adjust(value));

                that.trigger(SPIN);
            },

            _toggleHover: function (e) {
                $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
            },

            _toggleText: function (toggle) {
                var that = this;

                that._text.toggle(toggle);
                that.element.toggle(!toggle);
            },

            _parse: function (value, culture) {
                return parse(value, this._culture(culture), this.options.format);
            },

            _update: function (value) {
                var that = this,
                options = that.options,
                format = options.format,
                decimals = options.decimals,
                culture = that._culture(),
                numberFormat = that._format(format, culture),
                isNotNull;

                if (decimals === NULL) {
                    decimals = numberFormat.decimals;
                }

                value = that._parse(value, culture);

                isNotNull = value !== NULL;

                if (isNotNull) {
                    value = parseFloat(round(value, decimals));
                }

                that._value = value = that._adjust(value);
                that._placeholder(kendo.toString(value, format, culture));

                if (isNotNull) {
                    value = value.toString();
                    if (value.indexOf("e") !== -1) {
                        value = round(+value, decimals);
                    }
                    value = value.replace(POINT, numberFormat[POINT]);
                } else {
                    value = "";
                }

                that.element.val(value).attr("aria-valuenow", value);
            },

            _placeholder: function (value) {
                this._text.val(value);
                if (!placeholderSupported && !value) {
                    this._text.val(this.options.placeholder);
                }
            },

            _wrapper: function () {
                var that = this,
                element = that.element,
                DOMElement = element[0],
                wrapper;

                wrapper = element.parents(".k-numerictextbox");

                if (!wrapper.is("span.k-numerictextbox")) {
                    wrapper = element.hide().wrap('<span class="k-numeric-wrap k-state-default" />').parent();
                    wrapper = wrapper.wrap("<span/>").parent();
                }

                wrapper[0].style.cssText = DOMElement.style.cssText;
                DOMElement.style.width = "";
                that.wrapper = wrapper.addClass("k-widget k-numerictextbox")
                                  .addClass(DOMElement.className)
                                  .css("display", "");

                that._inputWrapper = $(wrapper[0].firstChild);
            },

            _reset: function () {
                var that = this,
                element = that.element,
                formId = element.attr("form"),
                form = formId ? $("#" + formId) : element.closest("form");

                if (form[0]) {
                    that._resetHandler = function () {
                        setTimeout(function () {
                            that.value(element[0].value);
                        });
                    };

                    that._form = form.on("reset", that._resetHandler);
                }
            }
        });

        function buttonHtml(className, text) {
            return '<span unselectable="on" class="k-link"><span unselectable="on" class="k-icon k-i-arrow-' + className + '" title="' + text + '">' + text + '</span></span>';
        }

        ui.plugin(NumericTextBox);
    })(window.kendo.jQuery);





    /* jshint eqnull: true */
    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        proxy = $.proxy,
        POPUP = "kendoPopup",
        INIT = "init",
        NS = ".kendoFilterMenu",
        EQ = "Is equal to",
        NEQ = "Is not equal to",
        roles = {
            "number": "numerictextbox",
            "date": "datepicker"
        },
        mobileRoles = {
            "string": "text",
            "number": "number",
            "date": "date"
        },
        isFunction = kendo.isFunction,
        Widget = ui.Widget;

        var booleanTemplate =
            '<div>' +
                '<div class="k-filter-help-text">#=messages.info#</div>' +
                '<label>' +
                    '<input type="radio" data-#=ns#bind="checked: filters[0].value" value="true" name="filters[0].value"/>' +
                    '#=messages.isTrue#' +
                '</label>' +
                '<label>' +
                    '<input type="radio" data-#=ns#bind="checked: filters[0].value" value="false" name="filters[0].value"/>' +
                    '#=messages.isFalse#' +
                '</label>' +
                '<div>' +
                '<button type="submit" class="k-button k-primary">#=messages.filter#</button>' +
                '<button type="reset" class="k-button">#=messages.clear#</button>' +
                '</div>' +
            '</div>';

        var defaultTemplate =
            '<div>' +
                '<div class="k-filter-help-text">#=messages.info#</div>' +
                '<select data-#=ns#bind="value: filters[0].operator" data-#=ns#role="dropdownlist">' +
                    '#for(var op in operators){#' +
                        '<option value="#=op#">#=operators[op]#</option>' +
                    '#}#' +
                '</select>' +
                '#if(values){#' +
                    '<select data-#=ns#bind="value:filters[0].value" data-#=ns#text-field="text" data-#=ns#value-field="value" data-#=ns#source=\'#=kendo.stringify(values).replace(/\'/g,"&\\#39;")#\' data-#=ns#role="dropdownlist" data-#=ns#option-label="#=messages.selectValue#">' +
                    '</select>' +
                '#}else{#' +
                    '<input data-#=ns#bind="value:filters[0].value" class="k-textbox" type="text" #=role ? "data-" + ns + "role=\'" + role + "\'" : ""# />' +
                '#}#' +
                '#if(extra){#' +
                    '<select class="k-filter-and" data-#=ns#bind="value: logic" data-#=ns#role="dropdownlist">' +
                        '<option value="and">#=messages.and#</option>' +
                        '<option value="or">#=messages.or#</option>' +
                    '</select>' +
                    '<select data-#=ns#bind="value: filters[1].operator" data-#=ns#role="dropdownlist">' +
                        '#for(var op in operators){#' +
                            '<option value="#=op#">#=operators[op]#</option>' +
                        '#}#' +
                    '</select>' +
                    '#if(values){#' +
                        '<select data-#=ns#bind="value:filters[1].value" data-#=ns#text-field="text" data-#=ns#value-field="value" data-#=ns#source=\'#=kendo.stringify(values).replace(/\'/g,"&\\#39;")#\' data-#=ns#role="dropdownlist" data-#=ns#option-label="#=messages.selectValue#">' +
                        '</select>' +
                    '#}else{#' +
                        '<input data-#=ns#bind="value: filters[1].value" class="k-textbox" type="text" #=role ? "data-" + ns + "role=\'" + role + "\'" : ""#/>' +
                    '#}#' +
                '#}#' +
                '<div>' +
                '<button type="submit" class="k-button k-primary">#=messages.filter#</button>' +
                '<button type="reset" class="k-button">#=messages.clear#</button>' +
                '</div>' +
            '</div>';

        var defaultMobileTemplate =
            '<div data-#=ns#role="view" data-#=ns#init-widgets="false" class="k-grid-filter-menu">' +
                '<div data-#=ns#role="header" class="k-header">' +
                    '<button class="k-button k-cancel">#=messages.cancel#</button>' +
                    '#=field#' +
                    '<button type="submit" class="k-button k-submit">#=messages.filter#</button>' +
                '</div>' +
                '<form class="k-filter-menu k-mobile-list">' +
                    '<ul class="k-filter-help-text"><li><span class="k-link">#=messages.info#</span>' +
                    '<ul>' +
                        '<li class="k-item"><label class="k-label">#=messages.operator#' +
                            '<select data-#=ns#bind="value: filters[0].operator">' +
                                '#for(var op in operators){#' +
                                    '<option value="#=op#">#=operators[op]#</option>' +
                                '#}#' +
                            '</select>' +
                        '</label></li>' +
                        '<li class="k-item"><label class="k-label">#=messages.value#' +
                            '#if(values){#' +
                                '<select data-#=ns#bind="value:filters[0].value">' +
                                    '<option value="">#=messages.selectValue#</option>' +
                                    '#for(var val in values){#' +
                                        '<option value="#=values[val].value#">#=values[val].text#</option>' +
                                    '#}#' +
                                '</select>' +
                            '#}else{#' +
                                '<input data-#=ns#bind="value:filters[0].value" class="k-textbox" type="#=inputType#" ' +
                                '#=useRole ? "data-" + ns + "role=\'" + role + "\'" : ""# />' +
                            '#}#' +
                        '</label></li>' +
                        '#if(extra){#' +
                        '</ul>' +
                        '<ul class="k-filter-help-text"><li><span class="k-link"></span>' +
                            '<li class="k-item"><label class="k-label"><input type="radio" name="logic" class="k-check" data-#=ns#bind="checked: logic" value="and" />#=messages.and#</label></li>' +
                            '<li class="k-item"><label class="k-label"><input type="radio" name="logic" class="k-check" data-#=ns#bind="checked: logic" value="or" />#=messages.or#</label></li>' +
                        '</ul>' +
                        '<ul class="k-filter-help-text"><li><span class="k-link"></span>' +
                            '<li class="k-item"><label class="k-label">#=messages.operator#' +
                                '<select data-#=ns#bind="value: filters[1].operator">' +
                                    '#for(var op in operators){#' +
                                        '<option value="#=op#">#=operators[op]#</option>' +
                                    '#}#' +
                                '</select>' +
                            '</label></li>' +
                            '<li class="k-item"><label class="k-label">#=messages.value#' +
                                '#if(values){#' +
                                    '<select data-#=ns#bind="value:filters[1].value">' +
                                        '<option value="">#=messages.selectValue#</option>' +
                                        '#for(var val in values){#' +
                                            '<option value="#=values[val].value#">#=values[val].text#</option>' +
                                        '#}#' +
                                    '</select>' +
                                '#}else{#' +
                                    '<input data-#=ns#bind="value:filters[1].value" class="k-textbox" type="#=inputType#" ' +
                                    '#=useRole ? "data-" + ns + "role=\'" + role + "\'" : ""# />' +
                                '#}#' +
                            '</label></li>' +
                        '#}#' +
                        '</ul>' +
                        '</li><li class="k-button-container">' +
                            '<button type="reset" class="k-button">#=messages.clear#</button>' +
                        '</li></ul>' +
                    '</div>' +
                '</form>' +
            '</div>';

        var booleanMobileTemplate =
            '<div data-#=ns#role="view" data-#=ns#init-widgets="false" class="k-grid-filter-menu">' +
                '<div data-#=ns#role="header" class="k-header">' +
                    '<button class="k-button k-cancel">#=messages.cancel#</button>' +
                    '#=field#' +
                    '<button type="submit" class="k-button k-submit">#=messages.filter#</button>' +
                '</div>' +
                '<form class="k-filter-menu k-mobile-list">' +
                    '<ul class="k-filter-help-text"><li><span class="k-link">#=messages.info#</span>' +
                    '<ul>' +
                        '<li class="k-item"><label class="k-label">' +
                            '<input class="k-check" type="radio" data-#=ns#bind="checked: filters[0].value" value="true" name="filters[0].value"/>' +
                            '#=messages.isTrue#' +
                        '</label></li>' +
                        '<li class="k-item"><label class="k-label">' +
                            '<input class="k-check" type="radio" data-#=ns#bind="checked: filters[0].value" value="false" name="filters[0].value"/>' +
                            '#=messages.isFalse#' +
                        '</label></li>' +
                    '</ul>' +
                    '</li><li class="k-button-container">' +
                        '<button type="reset" class="k-button">#=messages.clear#</button>' +
                    '</li></ul>' +
                '</form>' +
            '</div>';

        function removeFiltersForField(expression, field) {
            if (expression.filters) {
                expression.filters = $.grep(expression.filters, function (filter) {
                    removeFiltersForField(filter, field);
                    if (filter.filters) {
                        return filter.filters.length;
                    } else {
                        return filter.field != field;
                    }
                });
            }
        }

        function convertItems(items) {
            var idx,
            length,
            item,
            value,
            text,
            result;

            if (items && items.length) {
                result = [];
                for (idx = 0, length = items.length; idx < length; idx++) {
                    item = items[idx];
                    text = item.text || item.value || item;
                    value = item.value == null ? (item.text || item) : item.value;

                    result[idx] = { text: text, value: value };
                }
            }
            return result;
        }


        function clearFilter(filters, field) {
            return $.grep(filters, function (expr) {
                if (expr.filters) {
                    expr.filters = $.grep(expr.filters, function (nested) {
                        return nested.field != field;
                    });

                    return expr.filters.length;
                }
                return expr.field != field;
            });
        }

        var FilterMenu = Widget.extend({
            init: function (element, options) {
                var that = this,
                type = "string",
                operators,
                initial,
                link,
                field;

                Widget.fn.init.call(that, element, options);

                operators = that.operators = options.operators || {};

                element = that.element;
                options = that.options;

                if (!options.appendToElement) {
                    link = element.addClass("k-with-icon k-filterable").find(".k-grid-filter");

                    if (!link[0]) {
                        link = element.prepend('<a class="k-grid-filter" href="#"><span class="k-icon k-filter"/></a>').find(".k-grid-filter");
                    }

                    link.attr("tabindex", -1).on("click" + NS, proxy(that._click, that));
                }

                that.link = link || $();

                that.dataSource = options.dataSource;

                that.field = options.field || element.attr(kendo.attr("field"));

                that.model = that.dataSource.reader.model;

                that._parse = function (value) {
                    return value + "";
                };

                if (that.model && that.model.fields) {
                    field = that.model.fields[that.field];

                    if (field) {
                        type = field.type || "string";
                        if (field.parse) {
                            that._parse = proxy(field.parse, field);
                        }
                    }
                }

                if (options.values) {
                    type = "enums";
                }

                that.type = type;

                operators = operators[type] || options.operators[type];

                for (initial in operators) { // get the first operator
                    break;
                }

                that._defaultFilter = function () {
                    return { field: that.field, operator: initial || "eq", value: "" };
                };

                that._refreshHandler = proxy(that.refresh, that);

                that.dataSource.bind("change", that._refreshHandler);

                if (options.appendToElement) { // force creation if used in column menu
                    that._init();
                } else {
                    that.refresh(); //refresh if DataSource is fitered before menu is created
                }
            },

            _init: function () {
                var that = this,
                ui = that.options.ui,
                setUI = isFunction(ui),
                role;

                that.pane = that.options.pane;
                if (that.pane) {
                    that._isMobile = true;
                }

                if (!setUI) {
                    role = ui || roles[that.type];
                }

                if (that._isMobile) {
                    that._createMobileForm(role);
                } else {
                    that._createForm(role);
                }

                that.form
                .on("submit" + NS, proxy(that._submit, that))
                .on("reset" + NS, proxy(that._reset, that));

                if (setUI) {
                    that.form.find(".k-textbox")
                    .removeClass("k-textbox")
                    .each(function () {
                        ui($(this));
                    });
                }

                that.form
                 .find("[" + kendo.attr("role") + "=numerictextbox]")
                 .removeClass("k-textbox")
                 .end()
                 .find("[" + kendo.attr("role") + "=datetimepicker]")
                 .removeClass("k-textbox")
                 .end()
                 .find("[" + kendo.attr("role") + "=timepicker]")
                 .removeClass("k-textbox")
                 .end()
                 .find("[" + kendo.attr("role") + "=datepicker]")
                 .removeClass("k-textbox");

                that.refresh();

                that.trigger(INIT, { field: that.field, container: that.form });
            },

            _createForm: function (role) {
                var that = this,
                options = that.options,
                operators = that.operators || {},
                type = that.type;

                operators = operators[type] || options.operators[type];

                that.form = $('<form class="k-filter-menu"/>')
                .html(kendo.template(type === "boolean" ? booleanTemplate : defaultTemplate)({
                    field: that.field,
                    format: options.format,
                    ns: kendo.ns,
                    messages: options.messages,
                    extra: options.extra,
                    operators: operators,
                    type: type,
                    role: role,
                    values: convertItems(options.values)
                }));

                if (!options.appendToElement) {
                    that.popup = that.form[POPUP]({
                        anchor: that.link,
                        open: proxy(that._open, that),
                        activate: proxy(that._activate, that),
                        close: function () {
                            if (that.options.closeCallback) {
                                that.options.closeCallback(that.element);
                            }
                        }
                    }).data(POPUP);
                } else {
                    that.element.append(that.form);
                    that.popup = that.element.closest(".k-popup").data(POPUP);
                }

                that.form
                .on("keydown" + NS, proxy(that._keydown, that));
            },

            _createMobileForm: function (role) {
                var that = this,
                options = that.options,
                operators = that.operators || {},
                type = that.type;

                operators = operators[type] || options.operators[type];

                that.form = $("<div />")
                .html(kendo.template(type === "boolean" ? booleanMobileTemplate : defaultMobileTemplate)({
                    field: that.field,
                    format: options.format,
                    ns: kendo.ns,
                    messages: options.messages,
                    extra: options.extra,
                    operators: operators,
                    type: type,
                    role: role,
                    useRole: (!kendo.support.input.date && type === "date") || type === "number",
                    inputType: mobileRoles[type],
                    values: convertItems(options.values)
                }));

                that.view = that.pane.append(that.form.html());
                that.form = that.view.element.find("form");

                that.view.element
                .on("click", ".k-submit", function (e) {
                    that.form.submit();
                    e.preventDefault();
                })
                .on("click", ".k-cancel", function (e) {
                    that._closeForm();
                    e.preventDefault();
                });
            },

            refresh: function () {
                var that = this,
                expression = that.dataSource.filter() || { filters: [], logic: "and" };

                that.filterModel = kendo.observable({
                    logic: "and",
                    filters: [that._defaultFilter(), that._defaultFilter()]
                });

                if (that.form) {
                    //NOTE: binding the form element directly causes weird error in IE when grid is bound through MVVM and column is sorted
                    kendo.bind(that.form.children().first(), that.filterModel);
                }

                if (that._bind(expression)) {
                    that.link.addClass("k-state-active");
                } else {
                    that.link.removeClass("k-state-active");
                }
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                if (that.form) {
                    kendo.unbind(that.form);
                    kendo.destroy(that.form);
                    that.form.unbind(NS);
                    if (that.popup) {
                        that.popup.destroy();
                        that.popup = null;
                    }
                    that.form = null;
                }

                if (that.view) {
                    that.view.purge();
                    that.view = null;
                }

                that.link.unbind(NS);

                if (that._refreshHandler) {
                    that.dataSource.unbind("change", that._refreshHandler);
                    that.dataSource = null;
                }

                that.element = that.link = that._refreshHandler = that.filterModel = null;
            },

            _bind: function (expression) {
                var that = this,
                filters = expression.filters,
                idx,
                length,
                found = false,
                current = 0,
                filterModel = that.filterModel,
                currentFilter,
                filter;

                for (idx = 0, length = filters.length; idx < length; idx++) {
                    filter = filters[idx];
                    if (filter.field == that.field) {
                        filterModel.set("logic", expression.logic);

                        currentFilter = filterModel.filters[current];
                        if (!currentFilter) {
                            filterModel.filters.push({ field: that.field });
                            currentFilter = filterModel.filters[current];
                        }
                        currentFilter.set("value", that._parse(filter.value));
                        currentFilter.set("operator", filter.operator);

                        current++;
                        found = true;
                    } else if (filter.filters) {
                        found = found || that._bind(filter);
                    }
                }

                return found;
            },

            _merge: function (expression) {
                var that = this,
                logic = expression.logic || "and",
                filters = expression.filters,
                filter,
                result = that.dataSource.filter() || { filters: [], logic: "and" },
                idx,
                length;

                removeFiltersForField(result, that.field);

                filters = $.grep(filters, function (filter) {
                    return filter.value !== "" && filter.value != null;
                });

                for (idx = 0, length = filters.length; idx < length; idx++) {
                    filter = filters[idx];
                    filter.value = that._parse(filter.value);
                }

                if (filters.length) {
                    if (result.filters.length) {
                        expression.filters = filters;

                        if (result.logic !== "and") {
                            result.filters = [{ logic: result.logic, filters: result.filters}];
                            result.logic = "and";
                        }

                        if (filters.length > 1) {
                            result.filters.push(expression);
                        } else {
                            result.filters.push(filters[0]);
                        }
                    } else {
                        result.filters = filters;
                        result.logic = logic;
                    }
                }

                return result;
            },

            filter: function (expression) {
                expression = this._merge(expression);

                if (expression.filters.length) {
                    this.dataSource.filter(expression);
                }
            },

            clear: function () {
                var that = this,
                expression = that.dataSource.filter() || { filters: [] };

                expression.filters = $.grep(expression.filters, function (filter) {
                    if (filter.filters) {
                        filter.filters = clearFilter(filter.filters, that.field);

                        return filter.filters.length;
                    }

                    return filter.field != that.field;
                });

                if (!expression.filters.length) {
                    expression = null;
                }

                that.dataSource.filter(expression);
            },

            _submit: function (e) {
                e.preventDefault();
                e.stopPropagation();

                this.filter(this.filterModel.toJSON());

                this._closeForm();
            },

            _reset: function () {
                this.clear();

                this._closeForm();
            },

            _closeForm: function () {
                if (this._isMobile) {
                    this.pane.navigate("", this.options.animations.right);
                } else {
                    this.popup.close();
                }
            },

            _click: function (e) {
                e.preventDefault();
                e.stopPropagation();

                if (!this.popup && !this.pane) {
                    this._init();
                }

                if (this._isMobile) {
                    this.pane.navigate(this.view, this.options.animations.left);
                } else {
                    this.popup.toggle();
                }
            },

            _open: function () {
                var popup;

                $(".k-filter-menu").not(this.form).each(function () {
                    popup = $(this).data(POPUP);
                    if (popup) {
                        popup.close();
                    }
                });
            },

            _activate: function () {
                this.form.find(":kendoFocusable:first").focus();
            },

            _keydown: function (e) {
                if (e.keyCode == kendo.keys.ESC) {
                    this.popup.close();
                }
            },

            events: [INIT],

            options: {
                name: "FilterMenu",
                extra: true,
                appendToElement: false,
                type: "string",
                operators: {
                    string: {
                        eq: EQ,
                        neq: NEQ,
                        startswith: "Starts with",
                        contains: "Contains",
                        doesnotcontain: "Does not contain",
                        endswith: "Ends with"
                    },
                    number: {
                        eq: EQ,
                        neq: NEQ,
                        gte: "Is greater than or equal to",
                        gt: "Is greater than",
                        lte: "Is less than or equal to",
                        lt: "Is less than"
                    },
                    date: {
                        eq: EQ,
                        neq: NEQ,
                        gte: "Is after or equal to",
                        gt: "Is after",
                        lte: "Is before or equal to",
                        lt: "Is before"
                    },
                    enums: {
                        eq: EQ,
                        neq: NEQ
                    }
                },
                messages: {
                    info: "Show items with value that:",
                    isTrue: "is true",
                    isFalse: "is false",
                    filter: "Filter",
                    clear: "Clear",
                    and: "And",
                    or: "Or",
                    selectValue: "-Select value-",
                    operator: "Operator",
                    value: "Value",
                    cancel: "Cancel"
                },
                animations: {
                    left: "slide",
                    right: "slide:right"
                }
            }
        });

        ui.plugin(FilterMenu);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        activeElement = kendo._activeElement,
        touch = (kendo.support.touch && kendo.support.mobileOS),
        MOUSEDOWN = "mousedown",
        CLICK = "click",
        extend = $.extend,
        proxy = $.proxy,
        each = $.each,
        template = kendo.template,
        keys = kendo.keys,
        Widget = ui.Widget,
        excludedNodesRegExp = /^(ul|a|div)$/i,
        NS = ".kendoMenu",
        IMG = "img",
        OPEN = "open",
        MENU = "k-menu",
        LINK = "k-link",
        LAST = "k-last",
        CLOSE = "close",
        TIMER = "timer",
        FIRST = "k-first",
        IMAGE = "k-image",
        SELECT = "select",
        ZINDEX = "zIndex",
        ACTIVATE = "activate",
        DEACTIVATE = "deactivate",
        POINTERDOWN = "touchstart" + NS + " MSPointerDown" + NS + " pointerdown" + NS,
        pointers = kendo.support.pointers,
        msPointers = kendo.support.msPointers,
        allPointers = msPointers || pointers,
        MOUSEENTER = pointers ? "pointerover" : (msPointers ? "MSPointerOver" : "mouseenter"),
        MOUSELEAVE = pointers ? "pointerout" : (msPointers ? "MSPointerOut" : "mouseleave"),
        mobile = touch || allPointers,
        DOCUMENT_ELEMENT = $(document.documentElement),
        KENDOPOPUP = "kendoPopup",
        DEFAULTSTATE = "k-state-default",
        HOVERSTATE = "k-state-hover",
        FOCUSEDSTATE = "k-state-focused",
        DISABLEDSTATE = "k-state-disabled",
        menuSelector = ".k-menu",
        groupSelector = ".k-menu-group",
        popupSelector = groupSelector + ",.k-animation-container",
        allItemsSelector = ":not(.k-list) > .k-item",
        disabledSelector = ".k-item.k-state-disabled",
        itemSelector = ".k-item:not(.k-state-disabled)",
        linkSelector = ".k-item:not(.k-state-disabled) > .k-link",
        exclusionSelector = ":not(.k-item.k-separator)",
        nextSelector = exclusionSelector + ":eq(0)",
        lastSelector = exclusionSelector + ":last",
        templateSelector = "div:not(.k-animation-container,.k-list-container)",
        touchPointerTypes = { "2": 1, "touch": 1 },

        templates = {
            content: template(
                "<div class='k-content #= groupCssClass() #' tabindex='-1'>#= content(item) #</div>"
            ),
            group: template(
                "<ul class='#= groupCssClass(group) #'#= groupAttributes(group) # role='menu' aria-hidden='true'>" +
                    "#= renderItems(data) #" +
                "</ul>"
            ),
            itemWrapper: template(
                "<#= tag(item) # class='#= textClass(item) #'#= textAttributes(item) #>" +
                    "#= image(item) ##= sprite(item) ##= text(item) #" +
                    "#= arrow(data) #" +
                "</#= tag(item) #>"
            ),
            item: template(
                "<li class='#= wrapperCssClass(group, item) #' role='menuitem' #=item.items ? \"aria-haspopup='true'\": \"\"#" +
                    "#=item.enabled === false ? \"aria-disabled='true'\" : ''#>" +
                    "#= itemWrapper(data) #" +
                    "# if (item.items) { #" +
                    "#= subGroup({ items: item.items, menu: menu, group: { expanded: item.expanded } }) #" +
                    "# } else if (item.content || item.contentUrl) { #" +
                    "#= renderContent(data) #" +
                    "# } #" +
                "</li>"
            ),
            image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
            arrow: template("<span class='#= arrowClass(item, group) #'></span>"),
            sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
            empty: template("")
        },

        rendering = {

            wrapperCssClass: function (group, item) {
                var result = "k-item",
                    index = item.index;

                if (item.enabled === false) {
                    result += " k-state-disabled";
                } else {
                    result += " k-state-default";
                }

                if (group.firstLevel && index === 0) {
                    result += " k-first";
                }

                if (index == group.length - 1) {
                    result += " k-last";
                }

                if (item.cssClass) {
                    result += " " + item.cssClass;
                }

                return result;
            },

            textClass: function () {
                return LINK;
            },

            textAttributes: function (item) {
                return item.url ? " href='" + item.url + "'" : "";
            },

            arrowClass: function (item, group) {
                var result = "k-icon";

                if (group.horizontal) {
                    result += " k-i-arrow-s";
                } else {
                    result += " k-i-arrow-e";
                }

                return result;
            },

            text: function (item) {
                return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
            },

            tag: function (item) {
                return item.url ? "a" : "span";
            },

            groupAttributes: function (group) {
                return group.expanded !== true ? " style='display:none'" : "";
            },

            groupCssClass: function () {
                return "k-group k-menu-group";
            },

            content: function (item) {
                return item.content ? item.content : "&nbsp;";
            }
        };

        function getEffectDirection(direction, root) {
            direction = direction.split(" ")[!root + 0] || direction;
            return direction.replace("top", "up").replace("bottom", "down");
        }

        function parseDirection(direction, root, isRtl) {
            direction = direction.split(" ")[!root + 0] || direction;
            var output = { origin: ["bottom", (isRtl ? "right" : "left")], position: ["top", (isRtl ? "right" : "left")] },
            horizontal = /left|right/.test(direction);

            if (horizontal) {
                output.origin = ["top", direction];
                output.position[1] = kendo.directions[direction].reverse;
            } else {
                output.origin[0] = direction;
                output.position[0] = kendo.directions[direction].reverse;
            }

            output.origin = output.origin.join(" ");
            output.position = output.position.join(" ");

            return output;
        }

        function contains(parent, child) {
            try {
                return $.contains(parent, child);
            } catch (e) {
                return false;
            }
        }

        function updateItemClasses(item) {
            item = $(item);

            item.addClass("k-item")
            .children(IMG)
            .addClass(IMAGE);
            item
            .children("a")
            .addClass(LINK)
            .children(IMG)
            .addClass(IMAGE);
            item
            .filter(":not([disabled])")
            .addClass(DEFAULTSTATE);
            item
            .filter(".k-separator:empty")
            .append("&nbsp;");
            item
            .filter("li[disabled]")
            .addClass(DISABLEDSTATE)
            .removeAttr("disabled")
            .attr("aria-disabled", true);

            if (!item.filter("[role]").length) {
                item.attr("role", "menuitem");
            }

            if (!item.children("." + LINK).length) {
                item
                .contents()      // exclude groups, real links, templates and empty text nodes
                .filter(function () { return (!this.nodeName.match(excludedNodesRegExp) && !(this.nodeType == 3 && !$.trim(this.nodeValue))); })
                .wrapAll("<span class='" + LINK + "'/>");
            }

            updateArrow(item);
            updateFirstLast(item);
        }

        function updateArrow(item) {
            item = $(item);

            item.find("> .k-link > [class*=k-i-arrow]:not(.k-sprite)").remove();

            item.filter(":has(.k-menu-group)")
            .children(".k-link:not(:has([class*=k-i-arrow]:not(.k-sprite)))")
            .each(function () {
                var item = $(this),
                    parent = item.parent().parent();

                item.append("<span class='k-icon " + (parent.hasClass(MENU + "-horizontal") ? "k-i-arrow-s" : "k-i-arrow-e") + "'/>");
            });
        }

        function updateFirstLast(item) {
            item = $(item);

            item.filter(".k-first:not(:first-child)").removeClass(FIRST);
            item.filter(".k-last:not(:last-child)").removeClass(LAST);
            item.filter(":first-child").addClass(FIRST);
            item.filter(":last-child").addClass(LAST);
        }

        var Menu = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                element = that.wrapper = that.element;
                options = that.options;

                that._initData(options);

                that._updateClasses();

                that._animations(options);

                that.nextItemZIndex = 100;

                that._tabindex();

                that._focusProxy = proxy(that._focusHandler, that);

                element.on(POINTERDOWN, that._focusProxy)
                   .on(CLICK + NS, disabledSelector, false)
                   .on(CLICK + NS, itemSelector, proxy(that._click, that))
                   .on("keydown" + NS, proxy(that._keydown, that))
                   .on("focus" + NS, proxy(that._focus, that))
                   .on("focus" + NS, ".k-content", proxy(that._focus, that))
                   .on(POINTERDOWN + " " + MOUSEDOWN + NS, ".k-content", proxy(that._preventClose, that))
                   .on("blur" + NS, proxy(that._removeHoverItem, that))
                   .on("blur" + NS, "[tabindex]", proxy(that._checkActiveElement, that))
                   .on(MOUSEENTER + NS, itemSelector, proxy(that._mouseenter, that))
                   .on(MOUSELEAVE + NS, itemSelector, proxy(that._mouseleave, that))
                   .on(MOUSEENTER + NS + " " + MOUSELEAVE + NS + " " +
                       MOUSEDOWN + NS + " " + CLICK + NS, linkSelector, proxy(that._toggleHover, that));

                if (options.openOnClick) {
                    that.clicked = false;
                    that._documentClickHandler = proxy(that._documentClick, that);
                    $(document).click(that._documentClickHandler);
                }

                element.attr("role", "menubar");

                if (element[0].id) {
                    that._ariaId = kendo.format("{0}_mn_active", element[0].id);
                }

                kendo.notify(that);
            },

            events: [
            OPEN,
            CLOSE,
            ACTIVATE,
            DEACTIVATE,
            SELECT
        ],

            options: {
                name: "Menu",
                animation: {
                    open: {
                        duration: 200
                    },
                    close: { // if close animation effects are defined, they will be used instead of open.reverse
                        duration: 100
                    }
                },
                orientation: "horizontal",
                direction: "default",
                openOnClick: false,
                closeOnClick: true,
                hoverDelay: 100,
                popupCollision: undefined
            },

            _initData: function (options) {
                var that = this;

                if (options.dataSource) {
                    that.angular("cleanup", function () {
                        return {
                            elements: that.element.children()
                        };
                    });
                    that.element.empty();
                    that.append(options.dataSource, that.element);
                    that.angular("compile", function () {
                        return {
                            elements: that.element.children()
                        };
                    });
                }
            },

            setOptions: function (options) {
                var animation = this.options.animation;

                this._animations(options);

                options.animation = extend(true, animation, options.animation);

                if ("dataSource" in options) {
                    this._initData(options);
                }

                this._updateClasses();

                Widget.fn.setOptions.call(this, options);
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.element.off(NS);

                if (that._documentClickHandler) {
                    $(document).unbind("click", that._documentClickHandler);
                }

                kendo.destroy(that.element);
            },

            enable: function (element, enable) {
                this._toggleDisabled(element, enable !== false);

                return this;
            },

            disable: function (element) {
                this._toggleDisabled(element, false);

                return this;
            },

            append: function (item, referenceItem) {
                referenceItem = this.element.find(referenceItem);

                var inserted = this._insert(item, referenceItem, referenceItem.length ? referenceItem.find("> .k-menu-group, > .k-animation-container > .k-menu-group") : null);

                each(inserted.items, function () {
                    inserted.group.append(this);
                    updateArrow(this);
                });

                updateArrow(referenceItem);
                updateFirstLast(inserted.group.find(".k-first, .k-last").add(inserted.items));

                return this;
            },

            insertBefore: function (item, referenceItem) {
                referenceItem = this.element.find(referenceItem);

                var inserted = this._insert(item, referenceItem, referenceItem.parent());

                each(inserted.items, function () {
                    referenceItem.before(this);
                    updateArrow(this);
                    updateFirstLast(this);
                });

                updateFirstLast(referenceItem);

                return this;
            },

            insertAfter: function (item, referenceItem) {
                referenceItem = this.element.find(referenceItem);

                var inserted = this._insert(item, referenceItem, referenceItem.parent());

                each(inserted.items, function () {
                    referenceItem.after(this);
                    updateArrow(this);
                    updateFirstLast(this);
                });

                updateFirstLast(referenceItem);

                return this;
            },

            _insert: function (item, referenceItem, parent) {
                var that = this,
                items, groups;

                if (!referenceItem || !referenceItem.length) {
                    parent = that.element;
                }

                var plain = $.isPlainObject(item),
                groupData = {
                    firstLevel: parent.hasClass(MENU),
                    horizontal: parent.hasClass(MENU + "-horizontal"),
                    expanded: true,
                    length: parent.children().length
                };

                if (referenceItem && !parent.length) {
                    parent = $(Menu.renderGroup({ group: groupData })).appendTo(referenceItem);
                }

                if (plain || $.isArray(item)) { // is JSON
                    items = $($.map(plain ? [item] : item, function (value, idx) {
                        if (typeof value === "string") {
                            return $(value).get();
                        } else {
                            return $(Menu.renderItem({
                                group: groupData,
                                item: extend(value, { index: idx })
                            })).get();
                        }
                    }));
                } else {
                    if (typeof item == "string" && item[0] != "<") {
                        items = that.element.find(item);
                    } else {
                        items = $(item);
                    }

                    groups = items.find("> ul")
                                .addClass("k-menu-group")
                                .attr("role", "menu");

                    items = items.filter("li");

                    items.add(groups.find("> li")).each(function () {
                        updateItemClasses(this);
                    });
                }

                return { items: items, group: parent };
            },

            remove: function (element) {
                element = this.element.find(element);

                var that = this,
                parent = element.parentsUntil(that.element, allItemsSelector),
                group = element.parent("ul:not(.k-menu)");

                element.remove();

                if (group && !group.children(allItemsSelector).length) {
                    var container = group.parent(".k-animation-container");
                    if (container.length) {
                        container.remove();
                    } else {
                        group.remove();
                    }
                }

                if (parent.length) {
                    parent = parent.eq(0);

                    updateArrow(parent);
                    updateFirstLast(parent);
                }

                return that;
            },

            open: function (element) {
                var that = this,
                options = that.options,
                horizontal = options.orientation == "horizontal",
                direction = options.direction,
                isRtl = kendo.support.isRtl(that.wrapper);
                element = that.element.find(element);

                if (/^(top|bottom|default)$/.test(direction)) {
                    if (isRtl) {
                        direction = horizontal ? (direction + " left").replace("default", "bottom") : "left";
                    } else {
                        direction = horizontal ? (direction + " right").replace("default", "bottom") : "right";
                    }
                }

                element.siblings()
                   .find(">.k-popup:visible,>.k-animation-container>.k-popup:visible")
                   .each(function () {
                       var popup = $(this).data("kendoPopup");

                       if (popup) {
                           popup.close();
                       }
                   });

                element.each(function () {
                    var li = $(this);

                    clearTimeout(li.data(TIMER));

                    li.data(TIMER, setTimeout(function () {
                        var ul = li.find(".k-menu-group:first:hidden"),
                        popup;

                        if (ul[0] && that._triggerEvent({ item: li[0], type: OPEN }) === false) {

                            if (!ul.find(".k-menu-group")[0] && ul.children(".k-item").length > 1) {
                                var windowHeight = $(window).height(),
                                setScrolling = function () {
                                    ul.css({ maxHeight: windowHeight - (ul.outerHeight() - ul.height()) - kendo.getShadows(ul).bottom, overflow: "auto" });
                                };

                                if (kendo.support.browser.msie && kendo.support.browser.version <= 7) {
                                    setTimeout(setScrolling, 0); // timeout required by IE7
                                } else {
                                    setScrolling();
                                }
                            } else {
                                ul.css({ maxHeight: "", overflow: "" });
                            }

                            li.data(ZINDEX, li.css(ZINDEX));
                            li.css(ZINDEX, that.nextItemZIndex++);

                            popup = ul.data(KENDOPOPUP);
                            var root = li.parent().hasClass(MENU),
                            parentHorizontal = root && horizontal,
                            directions = parseDirection(direction, root, isRtl),
                            effects = options.animation.open.effects,
                            openEffects = effects !== undefined ? effects : "slideIn:" + getEffectDirection(direction, root);

                            if (!popup) {
                                popup = ul.kendoPopup({
                                    activate: function () { that._triggerEvent({ item: this.wrapper.parent(), type: ACTIVATE }); },
                                    deactivate: function (e) {
                                        e.sender.element // Restore opacity after fade.
                                        .removeData("targetTransform")
                                        .css({ opacity: "" });
                                        that._triggerEvent({ item: this.wrapper.parent(), type: DEACTIVATE });
                                    },
                                    origin: directions.origin,
                                    position: directions.position,
                                    collision: options.popupCollision !== undefined ? options.popupCollision : (parentHorizontal ? "fit" : "fit flip"),
                                    anchor: li,
                                    appendTo: li,
                                    animation: {
                                        open: extend(true, { effects: openEffects }, options.animation.open),
                                        close: options.animation.close
                                    },
                                    close: function (e) {
                                        var li = e.sender.wrapper.parent();

                                        if (!that._triggerEvent({ item: li[0], type: CLOSE })) {
                                            li.css(ZINDEX, li.data(ZINDEX));
                                            li.removeData(ZINDEX);

                                            if (mobile) {
                                                li.removeClass(HOVERSTATE);
                                                that._removeHoverItem();
                                            }
                                        } else {
                                            e.preventDefault();
                                        }
                                    }
                                }).data(KENDOPOPUP);
                            } else {
                                popup = ul.data(KENDOPOPUP);
                                popup.options.origin = directions.origin;
                                popup.options.position = directions.position;
                                popup.options.animation.open.effects = openEffects;
                            }
                            ul.removeAttr("aria-hidden");
                            popup.open();
                        }

                    }, that.options.hoverDelay));
                });

                return that;
            },

            close: function (items, dontClearClose) {
                var that = this,
                element = that.element;

                items = element.find(items);

                if (!items.length) {
                    items = element.find(">.k-item");
                }

                items.each(function () {
                    var li = $(this);

                    if (!dontClearClose && that._isRootItem(li)) {
                        that.clicked = false;
                    }

                    clearTimeout(li.data(TIMER));

                    li.data(TIMER, setTimeout(function () {
                        var popup = li.find(".k-menu-group:not(.k-list-container):not(.k-calendar-container):first:visible").data(KENDOPOPUP);

                        if (popup) {
                            popup.close();
                            popup.element.attr("aria-hidden", true);
                        }
                    }, that.options.hoverDelay));
                });

                return that;
            },

            _toggleDisabled: function (items, enable) {
                this.element.find(items).each(function () {
                    $(this)
                    .toggleClass(DEFAULTSTATE, enable)
                    .toggleClass(DISABLEDSTATE, !enable)
                    .attr("aria-disabled", !enable);
                });
            },

            _toggleHover: function (e) {
                var target = $(kendo.eventTarget(e) || e.target).closest(allItemsSelector),
                isEnter = e.type == MOUSEENTER || MOUSEDOWN.indexOf(e.type) !== -1;

                if (!target.parents("li." + DISABLEDSTATE).length) {
                    target.toggleClass(HOVERSTATE, isEnter || e.type == "mousedown" || e.type == "click");
                }

                this._removeHoverItem();
            },

            _preventClose: function () {
                if (!this.options.closeOnClick) {
                    this._closurePrevented = true;
                }
            },

            _checkActiveElement: function (e) {
                var that = this,
                hoverItem = $(e ? e.currentTarget : this._hoverItem()),
                target = that._findRootParent(hoverItem)[0];

                if (!this._closurePrevented) {
                    setTimeout(function () {
                        if (!document.hasFocus() || (!contains(target, kendo._activeElement()) && e && !contains(target, e.currentTarget))) {
                            that.close(target);
                        }
                    }, 0);
                }

                this._closurePrevented = false;
            },

            _removeHoverItem: function () {
                var oldHoverItem = this._hoverItem();

                if (oldHoverItem && oldHoverItem.hasClass(FOCUSEDSTATE)) {
                    oldHoverItem.removeClass(FOCUSEDSTATE);
                    this._oldHoverItem = null;
                }
            },

            _updateClasses: function () {
                var element = this.element,
                nonContentGroupsSelector = ".k-menu-init div ul",
                items;

                element.removeClass("k-menu-horizontal k-menu-vertical");
                element.addClass("k-widget k-reset k-header k-menu-init " + MENU).addClass(MENU + "-" + this.options.orientation);

                element.find("li > ul")
                   .filter(function () {
                       return !kendo.support.matchesSelector.call(this, nonContentGroupsSelector);
                   })
                   .addClass("k-group k-menu-group")
                   .attr("role", "menu")
                   .attr("aria-hidden", element.is(":visible"))
                   .end()
                   .find("li > div")
                   .addClass("k-content")
                   .attr("tabindex", "-1"); // Capture the focus before the Menu

                items = element.find("> li,.k-menu-group > li");

                element.removeClass("k-menu-init");

                items.each(function () {
                    updateItemClasses(this);
                });
            },

            _mouseenter: function (e) {
                var that = this,
                element = $(e.currentTarget),
                hasChildren = (element.children(".k-animation-container").length || element.children(groupSelector).length);

                if (e.delegateTarget != element.parents(menuSelector)[0]) {
                    return;
                }

                if ((!that.options.openOnClick || that.clicked) && !touch && !((pointers || msPointers) &&
                e.originalEvent.pointerType in touchPointerTypes && that._isRootItem(element.closest(allItemsSelector)))) {
                    if (!contains(e.currentTarget, e.relatedTarget) && hasChildren) {
                        that.open(element);
                    }
                }

                if (that.options.openOnClick && that.clicked || mobile) {
                    element.siblings().each(proxy(function (_, sibling) {
                        that.close(sibling, true);
                    }, that));
                }
            },

            _mouseleave: function (e) {
                var that = this,
                element = $(e.currentTarget),
                hasChildren = (element.children(".k-animation-container").length || element.children(groupSelector).length);

                if (element.parentsUntil(".k-animation-container", ".k-list-container,.k-calendar-container")[0]) {
                    e.stopImmediatePropagation();
                    return;
                }

                if (!that.options.openOnClick && !touch && !((pointers || msPointers) &&
                e.originalEvent.pointerType in touchPointerTypes) &&
                !contains(e.currentTarget, e.relatedTarget || e.target) && hasChildren &&
                !contains(e.currentTarget, kendo._activeElement())) {
                    that.close(element);
                }
            },

            _click: function (e) {
                var that = this, openHandle,
                options = that.options,
                target = $(kendo.eventTarget(e)),
                nodeName = target[0] ? target[0].nodeName.toUpperCase() : "",
                formNode = (nodeName == "INPUT" || nodeName == "SELECT" || nodeName == "BUTTON" || nodeName == "LABEL"),
                link = target.closest("." + LINK),
                element = target.closest(allItemsSelector),
                href = link.attr("href"), childGroup, childGroupVisible,
                targetHref = target.attr("href"),
                sampleHref = $("<a href='#' />").attr("href"),
                isLink = (!!href && href !== sampleHref),
                isLocalLink = isLink && !!href.match(/^#/),
                isTargetLink = (!!targetHref && targetHref !== sampleHref),
                shouldCloseTheRootItem = (options.openOnClick && childGroupVisible && that._isRootItem(element));

                if (!options.openOnClick && element.children(templateSelector)[0]) {
                    return;
                }

                if (element.hasClass(DISABLEDSTATE)) {
                    e.preventDefault();
                    return;
                }

                if (!e.handled && that._triggerEvent({ item: element[0], type: SELECT }) && !formNode) { // We shouldn't stop propagation and shoudn't prevent form elements.
                    e.preventDefault();
                }

                e.handled = true;

                childGroup = element.children(popupSelector);
                childGroupVisible = childGroup.is(":visible");

                if (options.closeOnClick && (!isLink || isLocalLink) && (!childGroup.length || shouldCloseTheRootItem)) {
                    element.removeClass(HOVERSTATE).css("height"); // Force refresh for Chrome
                    that._oldHoverItem = that._findRootParent(element);
                    that.close(link.parentsUntil(that.element, allItemsSelector));
                    that.clicked = false;
                    if ("MSPointerUp".indexOf(e.type) != -1) {
                        e.preventDefault();
                    }
                    return;
                }

                if (isLink && e.enterKey) {
                    link[0].click();
                }

                if ((!that._isRootItem(element) || !options.openOnClick) && !kendo.support.touch && !((pointers || msPointers) && that._isRootItem(element.closest(allItemsSelector)))) {
                    return;
                }

                if (!isLink && !formNode && !isTargetLink) {
                    e.preventDefault();
                }

                that.clicked = true;
                openHandle = childGroup.is(":visible") ? CLOSE : OPEN;
                if (!options.closeOnClick && openHandle == CLOSE) {
                    return;
                }
                that[openHandle](element);
            },

            _documentClick: function (e) {
                if (contains(this.element[0], e.target)) {
                    return;
                }

                this.clicked = false;
            },

            _focus: function (e) {
                var that = this,
                target = e.target,
                hoverItem = that._hoverItem(),
                active = activeElement();

                if (target != that.wrapper[0] && !$(target).is(":kendoFocusable")) {
                    e.stopPropagation();
                    $(target).closest(".k-content").closest(".k-menu-group").closest(".k-item").addClass(FOCUSEDSTATE);
                    that.wrapper.focus();
                    return;
                }

                if (active === e.currentTarget) {
                    if (hoverItem.length) {
                        that._moveHover([], hoverItem);
                    } else if (!that._oldHoverItem) {
                        that._moveHover([], that.wrapper.children().first());
                    }
                }
            },

            _keydown: function (e) {
                var that = this,
                key = e.keyCode,
                hoverItem = that._oldHoverItem,
                target,
                belongsToVertical,
                hasChildren,
                isRtl = kendo.support.isRtl(that.wrapper);

                if (e.target != e.currentTarget && key != keys.ESC) {
                    return;
                }

                if (!hoverItem) {
                    hoverItem = that._oldHoverItem = that._hoverItem();
                }

                belongsToVertical = that._itemBelongsToVertival(hoverItem);
                hasChildren = that._itemHasChildren(hoverItem);

                if (key == keys.RIGHT) {
                    target = that[isRtl ? "_itemLeft" : "_itemRight"](hoverItem, belongsToVertical, hasChildren);
                } else if (key == keys.LEFT) {
                    target = that[isRtl ? "_itemRight" : "_itemLeft"](hoverItem, belongsToVertical, hasChildren);
                } else if (key == keys.DOWN) {
                    target = that._itemDown(hoverItem, belongsToVertical, hasChildren);
                } else if (key == keys.UP) {
                    target = that._itemUp(hoverItem, belongsToVertical, hasChildren);
                } else if (key == keys.ESC) {
                    target = that._itemEsc(hoverItem, belongsToVertical);
                } else if (key == keys.ENTER || key == keys.SPACEBAR) {
                    target = hoverItem.children(".k-link");
                    if (target.length > 0) {
                        that._click({ target: target[0], preventDefault: function () { }, enterKey: true });
                        that._moveHover(hoverItem, that._findRootParent(hoverItem));
                    }
                } else if (key == keys.TAB) {
                    target = that._findRootParent(hoverItem);
                    that._moveHover(hoverItem, target);
                    that._checkActiveElement();
                    return;
                }

                if (target && target[0]) {
                    e.preventDefault();
                    e.stopPropagation(); // needed to handle ESC in column menu only when a root item is focused
                }
            },

            _hoverItem: function () {
                return this.wrapper.find(".k-item.k-state-hover,.k-item.k-state-focused").filter(":visible");
            },

            _itemBelongsToVertival: function (item) {
                var menuIsVertical = this.wrapper.hasClass("k-menu-vertical");

                if (!item.length) {
                    return menuIsVertical;
                }
                return item.parent().hasClass("k-menu-group") || menuIsVertical;
            },

            _itemHasChildren: function (item) {
                if (!item.length) {
                    return false;
                }
                return item.children("ul.k-menu-group, div.k-animation-container").length > 0;
            },

            _moveHover: function (item, nextItem) {
                var that = this,
                id = that._ariaId;

                if (item.length && nextItem.length) {
                    item.removeClass(FOCUSEDSTATE);
                }

                if (nextItem.length) {
                    if (nextItem[0].id) {
                        id = nextItem[0].id;
                    }

                    nextItem.addClass(FOCUSEDSTATE);
                    that._oldHoverItem = nextItem;

                    if (id) {
                        that.element.removeAttr("aria-activedescendant");
                        $("#" + id).removeAttr("id");
                        nextItem.attr("id", id);
                        that.element.attr("aria-activedescendant", id);
                    }
                }
            },

            _findRootParent: function (item) {
                if (this._isRootItem(item)) {
                    return item;
                } else {
                    return item.parentsUntil(menuSelector, "li.k-item").last();
                }
            },

            _isRootItem: function (item) {
                return item.parent().hasClass(MENU);
            },

            _itemRight: function (item, belongsToVertical, hasChildren) {
                var that = this,
                nextItem,
                parentItem;

                if (item.hasClass(DISABLEDSTATE)) {
                    return;
                }

                if (!belongsToVertical) {
                    nextItem = item.nextAll(nextSelector);
                    if (!nextItem.length) {
                        nextItem = item.prevAll(lastSelector);
                    }
                } else if (hasChildren) {
                    that.open(item);
                    nextItem = item.find(".k-menu-group").children().first();
                } else if (that.options.orientation == "horizontal") {
                    parentItem = that._findRootParent(item);
                    that.close(parentItem);
                    nextItem = parentItem.nextAll(nextSelector);
                }

                if (nextItem && !nextItem.length) {
                    nextItem = that.wrapper.children(".k-item").first();
                } else if (!nextItem) {
                    nextItem = [];
                }

                that._moveHover(item, nextItem);
                return nextItem;
            },

            _itemLeft: function (item, belongsToVertical) {
                var that = this,
                nextItem;

                if (!belongsToVertical) {
                    nextItem = item.prevAll(nextSelector);
                    if (!nextItem.length) {
                        nextItem = item.nextAll(lastSelector);
                    }
                } else {
                    nextItem = item.parent().closest(".k-item");
                    that.close(nextItem);
                    if (that._isRootItem(nextItem) && that.options.orientation == "horizontal") {
                        nextItem = nextItem.prevAll(nextSelector);
                    }
                }

                if (!nextItem.length) {
                    nextItem = that.wrapper.children(".k-item").last();
                }

                that._moveHover(item, nextItem);
                return nextItem;
            },

            _itemDown: function (item, belongsToVertical, hasChildren) {
                var that = this,
                nextItem;

                if (!belongsToVertical) {
                    if (!hasChildren || item.hasClass(DISABLEDSTATE)) {
                        return;
                    } else {
                        that.open(item);
                        nextItem = item.find(".k-menu-group").children().first();
                    }
                } else {
                    nextItem = item.nextAll(nextSelector);
                }

                if (!nextItem.length && item.length) {
                    nextItem = item.parent().children().first();
                } else if (!item.length) {
                    nextItem = that.wrapper.children(".k-item").first();
                }

                that._moveHover(item, nextItem);
                return nextItem;
            },

            _itemUp: function (item, belongsToVertical) {
                var that = this,
                nextItem;

                if (!belongsToVertical) {
                    return;
                } else {
                    nextItem = item.prevAll(nextSelector);
                }

                if (!nextItem.length && item.length) {
                    nextItem = item.parent().children().last();
                } else if (!item.length) {
                    nextItem = that.wrapper.children(".k-item").last();
                }

                that._moveHover(item, nextItem);
                return nextItem;
            },

            _itemEsc: function (item, belongsToVertical) {
                var that = this,
                nextItem;

                if (!belongsToVertical) {
                    return item;
                } else {
                    nextItem = item.parent().closest(".k-item");
                    that.close(nextItem);
                    that._moveHover(item, nextItem);
                }

                return nextItem;
            },

            _triggerEvent: function (e) {
                var that = this;

                return that.trigger(e.type, { type: e.type, item: e.item });
            },

            _focusHandler: function (e) {
                var that = this,
                item = $(kendo.eventTarget(e)).closest(allItemsSelector);

                setTimeout(function () {
                    that._moveHover([], item);
                    if (item.children(".k-content")[0]) {
                        item.parent().closest(".k-item").removeClass(FOCUSEDSTATE);
                    }
                }, 200);
            },

            _animations: function (options) {
                if (options && ("animation" in options) && !options.animation) {
                    options.animation = { open: { effects: {} }, close: { hide: true, effects: {}} };
                }
            }

        });

        // client-side rendering
        extend(Menu, {
            renderItem: function (options) {
                options = extend({ menu: {}, group: {} }, options);

                var empty = templates.empty,
                item = options.item;

                return templates.item(extend(options, {
                    image: item.imageUrl ? templates.image : empty,
                    sprite: item.spriteCssClass ? templates.sprite : empty,
                    itemWrapper: templates.itemWrapper,
                    renderContent: Menu.renderContent,
                    arrow: item.items || item.content ? templates.arrow : empty,
                    subGroup: Menu.renderGroup
                }, rendering));
            },

            renderGroup: function (options) {
                return templates.group(extend({
                    renderItems: function (options) {
                        var html = "",
                        i = 0,
                        items = options.items,
                        len = items ? items.length : 0,
                        group = extend({ length: len }, options.group);

                        for (; i < len; i++) {
                            html += Menu.renderItem(extend(options, {
                                group: group,
                                item: extend({ index: i }, items[i])
                            }));
                        }

                        return html;
                    }
                }, options, rendering));
            },

            renderContent: function (options) {
                return templates.content(extend(options, rendering));
            }
        });

        var ContextMenu = Menu.extend({
            init: function (element, options) {
                var that = this;

                Menu.fn.init.call(that, element, options);

                that.target = $(that.options.target);

                that._popup();
                that._wire();
            },
            options: {
                name: "ContextMenu",
                filter: null,
                showOn: "contextmenu",
                orientation: "vertical",
                alignToAnchor: false,
                target: "body"
            },

            events: [
            OPEN,
            CLOSE,
            ACTIVATE,
            DEACTIVATE,
            SELECT
        ],

            setOptions: function (options) {
                var that = this;

                Menu.fn.setOptions.call(that, options);

                that.target.off(that.showOn + NS, that._showProxy);

                if (that.userEvents) {
                    that.userEvents.destroy();
                }

                that.target = $(that.options.target);
                if (options.orientation && that.popup.wrapper[0]) {
                    that.popup.element.unwrap();
                }

                that._wire();

                Menu.fn.setOptions.call(this, options);
            },

            destroy: function () {
                var that = this;

                that.target.off(that.options.showOn + NS);
                DOCUMENT_ELEMENT.off(kendo.support.mousedown + NS, that._closeProxy);

                if (that.userEvents) {
                    that.userEvents.destroy();
                }

                Menu.fn.destroy.call(that);
            },

            open: function (x, y) {
                var that = this;

                x = $(x)[0];

                if (contains(that.element[0], $(x)[0])) { // call parent open for children elements
                    Menu.fn.open.call(that, x);
                } else {
                    if (that._triggerEvent({ item: that.element, type: OPEN }) === false) {
                        if (that.popup.visible() && that.options.filter) {
                            that.popup.close(true);
                        }

                        if (y !== undefined) {
                            that.popup.wrapper.hide();
                            that.popup.open(x, y);
                        } else {
                            that.popup.options.anchor = (x ? x : that.popup.anchor) || that.target;
                            that.popup.open();
                        }

                        DOCUMENT_ELEMENT.off(that.popup.downEvent, that.popup._mousedownProxy);
                        DOCUMENT_ELEMENT
                        .on(kendo.support.mousedown + NS, that._closeProxy);
                    }
                }

                return that;
            },

            close: function () {
                var that = this;

                if (contains(that.element[0], $(arguments[0])[0])) {
                    Menu.fn.close.call(that, arguments[0]);
                } else {
                    if (that.popup.visible()) {
                        if (that._triggerEvent({ item: that.element, type: CLOSE }) === false) {
                            that.popup.close();
                            DOCUMENT_ELEMENT.off(kendo.support.mousedown + NS, that._closeProxy);
                            that.unbind(SELECT, that._closeTimeoutProxy);
                        }
                    }
                }
            },

            _showHandler: function (e) {
                var ev = e, offset,
                that = this,
                options = that.options;

                if (e.event) {
                    ev = e.event;
                    ev.pageX = e.x.location;
                    ev.pageY = e.y.location;
                }

                if (contains(that.element[0], e.relatedTarget || e.target)) {
                    return;
                }

                that._eventOrigin = ev;

                ev.preventDefault();
                ev.stopImmediatePropagation();

                that.element.find("." + FOCUSEDSTATE).removeClass(FOCUSEDSTATE);

                if ((options.filter && kendo.support.matchesSelector.call(ev.currentTarget, options.filter)) || !options.filter) {
                    if (options.alignToAnchor) {
                        that.open(ev.currentTarget);
                    } else {
                        that.popup.options.anchor = ev.currentTarget;

                        if (that._targetChild) {
                            offset = that.target.offset();
                            that.open(ev.pageX - offset.left, ev.pageY - offset.top);
                        } else {
                            that.open(ev.pageX, ev.pageY);
                        }
                    }
                }
            },

            _closeHandler: function (e) {
                var that = this,
				options = that.options,
                target = $(e.relatedTarget || e.target),
				sameTarget = target.closest(that.target.selector)[0] == that.target[0],
                children = target.closest(itemSelector).children(popupSelector),
                containment = contains(that.element[0], target[0]);

                that._eventOrigin = e;

                if (that.popup.visible() && ((e.which !== 3 && sameTarget) || !sameTarget) && ((that.options.closeOnClick && !touch &&
                !((pointers || msPointers) && e.originalEvent.pointerType in touchPointerTypes) &&
                !children[0] && containment) || !containment)) {
                    if (containment) {
                        this.unbind(SELECT, this._closeTimeoutProxy);
                        that.bind(SELECT, that._closeTimeoutProxy);
                    } else {
                        that.close();
                    }
                }
            },

            _wire: function () {
                var that = this,
                options = that.options,
                target = that.target;

                that._showProxy = proxy(that._showHandler, that);
                that._closeProxy = proxy(that._closeHandler, that);
                that._closeTimeoutProxy = proxy(that.close, that);

                if (target[0]) {
                    if (kendo.support.mobileOS && options.showOn == "contextmenu") {
                        that.userEvents = new kendo.UserEvents(target, {
                            filter: options.filter,
                            allowSelection: false
                        });

                        target.on(options.showOn + NS, false);
                        that.userEvents.bind("hold", that._showProxy);
                    } else {
                        if (options.filter) {
                            target.on(options.showOn + NS, options.filter, that._showProxy);
                        } else {
                            target.on(options.showOn + NS, that._showProxy);
                        }
                    }
                }
            },

            _triggerEvent: function (e) {
                var that = this,
                anchor = $(that.popup.options.anchor)[0],
                origin = that._eventOrigin;

                that._eventOrigin = undefined;

                return that.trigger(e.type, extend({ type: e.type, item: e.item || this.element[0], target: anchor }, origin ? { event: origin} : {}));
            },

            _popup: function () {
                var that = this;

                that._triggerProxy = proxy(that._triggerEvent, that);

                that.popup = that.element
                            .addClass("k-context-menu")
                            .kendoPopup({
                                anchor: that.target || "body",
                                copyAnchorStyles: that.options.copyAnchorStyles,
                                collision: that.options.popupCollision || "fit",
                                animation: that.options.animation,
                                activate: that._triggerProxy,
                                deactivate: that._triggerProxy
                            }).data("kendoPopup");

                that._targetChild = contains(that.target[0], that.popup.element[0]);
            }
        });

        ui.plugin(Menu);
        ui.plugin(ContextMenu);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        proxy = $.proxy,
        extend = $.extend,
        grep = $.grep,
        map = $.map,
        inArray = $.inArray,
        ACTIVE = "k-state-selected",
        ASC = "asc",
        DESC = "desc",
        CHANGE = "change",
        INIT = "init",
        SELECT = "select",
        POPUP = "kendoPopup",
        FILTERMENU = "kendoFilterMenu",
        MENU = "kendoMenu",
        NS = ".kendoColumnMenu",
        Widget = ui.Widget;

        function trim(text) {
            return $.trim(text).replace(/&nbsp;/gi, "");
        }

        function toHash(arr, key) {
            var result = {};
            var idx, len, current;
            for (idx = 0, len = arr.length; idx < len; idx++) {
                current = arr[idx];
                result[current[key]] = current;
            }
            return result;
        }

        function leafColumns(columns) {
            var result = [];

            for (var idx = 0; idx < columns.length; idx++) {
                if (!columns[idx].columns) {
                    result.push(columns[idx]);
                    continue;
                }
                result = result.concat(leafColumns(columns[idx].columns));
            }

            return result;
        }

        var ColumnMenu = Widget.extend({
            init: function (element, options) {
                var that = this,
                link;

                Widget.fn.init.call(that, element, options);

                element = that.element;
                options = that.options;
                that.owner = options.owner;
                that.dataSource = options.dataSource;

                that.field = element.attr(kendo.attr("field"));
                that.title = element.attr(kendo.attr("title"));

                link = element.find(".k-header-column-menu");

                if (!link[0]) {
                    link = element.addClass("k-with-icon").prepend('<a class="k-header-column-menu" href="#"><span class="k-icon k-i-arrowhead-s"/></a>').find(".k-header-column-menu");
                }

                that.link = link
                .attr("tabindex", -1)
                .on("click" + NS, proxy(that._click, that));

                that.wrapper = $('<div class="k-column-menu"/>');
            },

            _init: function () {
                var that = this;

                that.pane = that.options.pane;
                if (that.pane) {
                    that._isMobile = true;
                }

                if (that._isMobile) {
                    that._createMobileMenu();
                } else {
                    that._createMenu();
                }

                that._angularItems("compile");

                that._sort();

                that._columns();

                that._filter();

                that._lockColumns();

                that.trigger(INIT, { field: that.field, container: that.wrapper });
            },

            events: [INIT],

            options: {
                name: "ColumnMenu",
                messages: {
                    sortAscending: "Sort Ascending",
                    sortDescending: "Sort Descending",
                    filter: "Filter",
                    columns: "Columns",
                    done: "Done",
                    settings: "Column Settings",
                    lock: "Lock",
                    unlock: "Unlock"
                },
                filter: "",
                columns: true,
                sortable: true,
                filterable: true,
                animations: {
                    left: "slide"
                }
            },

            _createMenu: function () {
                var that = this,
                options = that.options;

                that.wrapper.html(kendo.template(template)({
                    ns: kendo.ns,
                    messages: options.messages,
                    sortable: options.sortable,
                    filterable: options.filterable,
                    columns: that._ownerColumns(),
                    showColumns: options.columns,
                    lockedColumns: options.lockedColumns
                }));

                that.popup = that.wrapper[POPUP]({
                    anchor: that.link,
                    open: proxy(that._open, that),
                    activate: proxy(that._activate, that),
                    close: function () {
                        if (that.options.closeCallback) {
                            that.options.closeCallback(that.element);
                        }
                    }
                }).data(POPUP);

                that.menu = that.wrapper.children()[MENU]({
                    orientation: "vertical",
                    closeOnClick: false
                }).data(MENU);
            },

            _createMobileMenu: function () {
                var that = this,
                options = that.options;

                var html = kendo.template(mobileTemplate)({
                    ns: kendo.ns,
                    field: that.field,
                    title: that.title || that.field,
                    messages: options.messages,
                    sortable: options.sortable,
                    filterable: options.filterable,
                    columns: that._ownerColumns(),
                    showColumns: options.columns,
                    lockedColumns: options.lockedColumns
                });

                that.view = that.pane.append(html);

                that.wrapper = that.view.element.find(".k-column-menu");

                that.menu = new MobileMenu(that.wrapper.children(), {
                    pane: that.pane
                });

                that.view.element.on("click", ".k-done", function (e) {
                    that.close();
                    e.preventDefault();
                });

                if (that.options.lockedColumns) {
                    that.view.bind("show", function () {
                        that._updateLockedColumns();
                    });
                }
            },

            _angularItems: function (action) {
                var that = this;
                that.angular(action, function () {
                    var items = that.wrapper.find(".k-columns-item input[" + kendo.attr("field") + "]").map(function () {
                        return $(this).closest("li");
                    });
                    var data = map(that._ownerColumns(), function (col) {
                        return { column: col._originalObject };
                    });
                    return {
                        elements: items,
                        data: data
                    };
                });
            },

            destroy: function () {
                var that = this;

                that._angularItems("cleanup");

                Widget.fn.destroy.call(that);

                if (that.filterMenu) {
                    that.filterMenu.destroy();
                }

                if (that._refreshHandler) {
                    that.dataSource.unbind(CHANGE, that._refreshHandler);
                }

                if (that.options.columns && that.owner && that._updateColumnsMenuHandler) {
                    that.owner.unbind("columnShow", that._updateColumnsMenuHandler);
                    that.owner.unbind("columnHide", that._updateColumnsMenuHandler);
                }

                if (that.menu) {
                    that.menu.element.off(NS);
                    that.menu.destroy();
                }

                that.wrapper.off(NS);

                if (that.popup) {
                    that.popup.destroy();
                }

                if (that.view) {
                    that.view.purge();
                }

                that.link.off(NS);
                that.owner = null;
                that.wrapper = null;
                that.element = null;
            },

            close: function () {
                this.menu.close();
                if (this.popup) {
                    this.popup.close();
                    this.popup.element.off("keydown" + NS);
                }
            },

            _click: function (e) {
                e.preventDefault();
                e.stopPropagation();

                var options = this.options;

                if (options.filter && this.element.is(!options.filter)) {
                    return;
                }

                if (!this.popup && !this.pane) {
                    this._init();
                }

                if (this._isMobile) {
                    this.pane.navigate(this.view, this.options.animations.left);
                } else {
                    this.popup.toggle();
                }
            },

            _open: function () {
                var that = this;
                $(".k-column-menu").not(that.wrapper).each(function () {
                    $(this).data(POPUP).close();
                });
                that.popup.element.on("keydown" + NS, function (e) {
                    if (e.keyCode == kendo.keys.ESC) {
                        that.close();
                    }
                });

                if (that.options.lockedColumns) {
                    that._updateLockedColumns();
                }
            },

            _activate: function () {
                this.menu.element.focus();
            },

            _ownerColumns: function () {
                var columns = leafColumns(this.owner.columns),
                menuColumns = grep(columns, function (col) {
                    var result = true,
                        title = trim(col.title || "");

                    if (col.menu === false || (!col.field && !title.length)) {
                        result = false;
                    }

                    return result;
                });

                return map(menuColumns, function (col) {
                    return {
                        originalField: col.field,
                        field: col.field || col.title,
                        title: col.title || col.field,
                        hidden: col.hidden,
                        index: inArray(col, columns),
                        locked: !!col.locked,
                        _originalObject: col
                    };
                });
            },

            _sort: function () {
                var that = this;

                if (that.options.sortable) {
                    that.refresh();

                    that._refreshHandler = proxy(that.refresh, that);

                    that.dataSource.bind(CHANGE, that._refreshHandler);

                    that.menu.bind(SELECT, function (e) {
                        var item = $(e.item),
                        dir;

                        if (item.hasClass("k-sort-asc")) {
                            dir = ASC;
                        } else if (item.hasClass("k-sort-desc")) {
                            dir = DESC;
                        }

                        if (!dir) {
                            return;
                        }

                        item.parent().find(".k-sort-" + (dir == ASC ? DESC : ASC)).removeClass(ACTIVE);

                        that._sortDataSource(item, dir);

                        that.close();
                    });
                }
            },

            _sortDataSource: function (item, dir) {
                var that = this,
                sortable = that.options.sortable,
                compare = sortable.compare === null ? undefined : sortable.compare,
                dataSource = that.dataSource,
                idx,
                length,
                sort = dataSource.sort() || [];

                if (item.hasClass(ACTIVE) && sortable && sortable.allowUnsort !== false) {
                    item.removeClass(ACTIVE);
                    dir = undefined;
                } else {
                    item.addClass(ACTIVE);
                }

                if (sortable === true || sortable.mode === "single") {
                    sort = [{ field: that.field, dir: dir, compare: compare}];
                } else {
                    for (idx = 0, length = sort.length; idx < length; idx++) {
                        if (sort[idx].field === that.field) {
                            sort.splice(idx, 1);
                            break;
                        }
                    }
                    sort.push({ field: that.field, dir: dir, compare: compare });
                }

                dataSource.sort(sort);
            },

            _columns: function () {
                var that = this;

                if (that.options.columns) {

                    that._updateColumnsMenu();

                    that._updateColumnsMenuHandler = proxy(that._updateColumnsMenu, that);

                    that.owner.bind(["columnHide", "columnShow"], that._updateColumnsMenuHandler);

                    that.menu.bind(SELECT, function (e) {
                        var item = $(e.item),
                        input,
                        index,
                        column,
                        columns = leafColumns(that.owner.columns),
                        field;

                        if (that._isMobile) {
                            e.preventDefault();
                        }

                        if (!item.parent().closest("li.k-columns-item")[0]) {
                            return;
                        }

                        input = item.find(":checkbox");
                        if (input.attr("disabled")) {
                            return;
                        }

                        field = input.attr(kendo.attr("field"));

                        column = grep(columns, function (column) {
                            return column.field == field || column.title == field;
                        })[0];

                        if (column.hidden === true) {
                            that.owner.showColumn(column);
                        } else {
                            that.owner.hideColumn(column);
                        }
                    });
                }
            },

            _updateColumnsMenu: function () {
                var idx, length, current, checked, locked;
                var fieldAttr = kendo.attr("field"),
                lockedAttr = kendo.attr("locked"),
                visible = grep(this._ownerColumns(), function (field) {
                    return !field.hidden;
                }),
                visibleDataFields = grep(visible, function (field) {
                    return field.originalField;
                }),
                lockedCount = grep(visibleDataFields, function (col) {
                    return col.locked === true;
                }).length,
                nonLockedCount = grep(visibleDataFields, function (col) {
                    return col.locked !== true;
                }).length;

                visible = map(visible, function (col) {
                    return col.field;
                });

                var checkboxes = this.wrapper
                .find(".k-columns-item input[" + fieldAttr + "]")
                .prop("disabled", false)
                .prop("checked", false);

                for (idx = 0, length = checkboxes.length; idx < length; idx++) {
                    current = checkboxes.eq(idx);
                    locked = current.attr(lockedAttr) === "true";
                    checked = false;
                    if (inArray(current.attr(fieldAttr), visible) > -1) {
                        checked = true;
                        current.prop("checked", checked);
                    }

                    if (checked) {
                        if (lockedCount == 1 && locked) {
                            current.prop("disabled", true);
                        }

                        if (nonLockedCount == 1 && !locked) {
                            current.prop("disabled", true);
                        }
                    }
                }
            },

            _updateColumnsLockedState: function () {
                var idx, length, current, locked, column;
                var fieldAttr = kendo.attr("field");
                var lockedAttr = kendo.attr("locked");
                var columns = toHash(this._ownerColumns(), "field");
                var checkboxes = this.wrapper
                .find(".k-columns-item input[type=checkbox]");

                for (idx = 0, length = checkboxes.length; idx < length; idx++) {
                    current = checkboxes.eq(idx);
                    column = columns[current.attr(fieldAttr)];
                    if (column) {
                        current.attr(lockedAttr, column.locked);
                    }
                }

                this._updateColumnsMenu();
            },

            _filter: function () {
                var that = this,
                options = that.options;

                if (options.filterable !== false) {
                    that.filterMenu = that.wrapper.find(".k-filterable")[FILTERMENU](
                    extend(true, {}, {
                        appendToElement: true,
                        dataSource: options.dataSource,
                        values: options.values,
                        field: that.field
                    },
                    options.filterable)
                    ).data(FILTERMENU);

                    if (that._isMobile) {
                        that.menu.bind(SELECT, function (e) {
                            var item = $(e.item);

                            if (item.hasClass("k-filter-item")) {
                                that.pane.navigate(that.filterMenu.view, that.options.animations.left);
                            }
                        });
                    }
                }
            },

            _lockColumns: function () {
                var that = this;
                that.menu.bind(SELECT, function (e) {
                    var item = $(e.item);

                    if (item.hasClass("k-lock")) {
                        that.owner.lockColumn(that.field);
                        that.close();
                    } else if (item.hasClass("k-unlock")) {
                        that.owner.unlockColumn(that.field);
                        that.close();
                    }
                });
            },

            _updateLockedColumns: function () {
                var field = this.field;
                var columns = this.owner.columns;
                var column = grep(columns, function (column) {
                    return column.field == field || column.title == field;
                })[0];

                if (!column) {
                    return;
                }

                var locked = column.locked === true;
                var length = grep(columns, function (column) {
                    return !column.hidden && ((column.locked && locked) || (!column.locked && !locked));
                }).length;

                var lockItem = this.wrapper.find(".k-lock").removeClass("k-state-disabled");
                var unlockItem = this.wrapper.find(".k-unlock").removeClass("k-state-disabled");

                if (locked || length == 1) {
                    lockItem.addClass("k-state-disabled");
                }

                if (!locked || length == 1) {
                    unlockItem.addClass("k-state-disabled");
                }

                this._updateColumnsLockedState();
            },

            refresh: function () {
                var that = this,
                sort = that.options.dataSource.sort() || [],
                descriptor,
                field = that.field,
                idx,
                length;

                that.wrapper.find(".k-sort-asc, .k-sort-desc").removeClass(ACTIVE);

                for (idx = 0, length = sort.length; idx < length; idx++) {
                    descriptor = sort[idx];

                    if (field == descriptor.field) {
                        that.wrapper.find(".k-sort-" + descriptor.dir).addClass(ACTIVE);
                    }
                }
            }
        });

        var template = '<ul>' +
                    '#if(sortable){#' +
                        '<li class="k-item k-sort-asc"><span class="k-link"><span class="k-sprite k-i-sort-asc"></span>${messages.sortAscending}</span></li>' +
                        '<li class="k-item k-sort-desc"><span class="k-link"><span class="k-sprite k-i-sort-desc"></span>${messages.sortDescending}</span></li>' +
                        '#if(showColumns || filterable){#' +
                            '<li class="k-separator"></li>' +
                        '#}#' +
                    '#}#' +
                    '#if(showColumns){#' +
                        '<li class="k-item k-columns-item"><span class="k-link"><span class="k-sprite k-i-columns"></span>${messages.columns}</span><ul>' +
                        '#for (var idx = 0; idx < columns.length; idx++) {#' +
                            '<li><input type="checkbox" data-#=ns#field="#=columns[idx].field.replace(/\"/g,"&\\#34;")#" data-#=ns#index="#=columns[idx].index#" data-#=ns#locked="#=columns[idx].locked#"/>#=columns[idx].title#</li>' +
                        '#}#' +
                        '</ul></li>' +
                        '#if(filterable || lockedColumns){#' +
                            '<li class="k-separator"></li>' +
                        '#}#' +
                    '#}#' +
                    '#if(filterable){#' +
                        '<li class="k-item k-filter-item"><span class="k-link"><span class="k-sprite k-filter"></span>${messages.filter}</span><ul>' +
                            '<li><div class="k-filterable"></div></li>' +
                        '</ul></li>' +
                        '#if(lockedColumns){#' +
                            '<li class="k-separator"></li>' +
                        '#}#' +
                    '#}#' +
                    '#if(lockedColumns){#' +
                        '<li class="k-item k-lock"><span class="k-link"><span class="k-sprite k-i-lock"></span>${messages.lock}</span></li>' +
                        '<li class="k-item k-unlock"><span class="k-link"><span class="k-sprite k-i-unlock"></span>${messages.unlock}</span></li>' +
                    '#}#' +
                    '</ul>';

        var mobileTemplate =
            '<div data-#=ns#role="view" data-#=ns#init-widgets="false" class="k-grid-column-menu">' +
                '<div data-#=ns#role="header" class="k-header">' +
                    '${messages.settings}' +
                    '<button class="k-button k-done">#=messages.done#</button>' +
                '</div>' +
                '<div class="k-column-menu k-mobile-list"><ul><li>' +
                    '<span class="k-link">${title}</span><ul>' +
                '#if(sortable){#' +
                    '<li class="k-item k-sort-asc"><span class="k-link"><span class="k-sprite k-i-sort-asc"></span>${messages.sortAscending}</span></li>' +
                    '<li class="k-item k-sort-desc"><span class="k-link"><span class="k-sprite k-i-sort-desc"></span>${messages.sortDescending}</span></li>' +
                '#}#' +
                '#if(lockedColumns){#' +
                    '<li class="k-item k-lock"><span class="k-link"><span class="k-sprite k-i-lock"></span>${messages.lock}</span></li>' +
                    '<li class="k-item k-unlock"><span class="k-link"><span class="k-sprite k-i-unlock"></span>${messages.unlock}</span></li>' +
                '#}#' +
                '#if(filterable){#' +
                    '<li class="k-item k-filter-item">' +
                        '<span class="k-link k-filterable">' +
                            '<span class="k-sprite k-filter"></span>' +
                            '${messages.filter}</span>' +
                    '</li>' +
                '#}#' +
                '</ul></li>' +
                '#if(showColumns){#' +
                    '<li class="k-columns-item"><span class="k-link">${messages.columns}</span><ul>' +
                    '#for (var idx = 0; idx < columns.length; idx++) {#' +
                        '<li class="k-item"><label class="k-label"><input type="checkbox" class="k-check" data-#=ns#field="#=columns[idx].field.replace(/\"/g,"&\\#34;")#" data-#=ns#index="#=columns[idx].index#" data-#=ns#locked="#=columns[idx].locked#"/>#=columns[idx].title#</label></li>' +
                    '#}#' +
                    '</ul></li>' +
                '#}#' +
                '</ul></div>' +
            '</div>';

        var MobileMenu = Widget.extend({
            init: function (element, options) {
                Widget.fn.init.call(this, element, options);

                this.element.on("click" + NS, "li:not(.k-separator):not(.k-state-disabled)", "_click");
            },

            events: [SELECT],

            _click: function (e) {
                e.preventDefault();

                this.trigger(SELECT, { item: e.currentTarget });
            },

            close: function () {
                this.options.pane.navigate("");
            },

            destroy: function () {
                Widget.fn.destroy.call(this);

                this.element.off(NS);
            }
        });

        ui.plugin(ColumnMenu);
    })(window.kendo.jQuery);



    ;

    (function ($, undefined) {
        var kendo = window.kendo;
        var ui = kendo.ui;
        var Widget = ui.Widget;
        var DIR = "dir";
        var ASC = "asc";
        var SINGLE = "single";
        var FIELD = "field";
        var DESC = "desc";
        var sorterNS = ".kendoColumnSorter";
        var TLINK = ".k-link";
        var ARIASORT = "aria-sort";
        var proxy = $.proxy;

        var ColumnSorter = Widget.extend({
            init: function (element, options) {

                var that = this, link;

                Widget.fn.init.call(that, element, options);

                that._refreshHandler = proxy(that.refresh, that);

                that.dataSource = that.options.dataSource.bind("change", that._refreshHandler);

                link = that.element.find(TLINK);

                if (!link[0]) {
                    link = that.element.wrapInner('<a class="k-link" href="#"/>').find(TLINK);
                }

                that.link = link;

                that.element.on("click" + sorterNS, proxy(that._click, that));
            },

            options: {
                name: "ColumnSorter",
                mode: SINGLE,
                allowUnsort: true,
                compare: null,
                filter: ""
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.element.off(sorterNS);

                that.dataSource.unbind("change", that._refreshHandler);
                that._refreshHandler = that.element = that.link = that.dataSource = null;
            },

            refresh: function () {
                var that = this,
                sort = that.dataSource.sort() || [],
                idx,
                length,
                descriptor,
                dir,
                element = that.element,
                field = element.attr(kendo.attr(FIELD));

                element.removeAttr(kendo.attr(DIR));
                element.removeAttr(ARIASORT);

                for (idx = 0, length = sort.length; idx < length; idx++) {
                    descriptor = sort[idx];

                    if (field == descriptor.field) {
                        element.attr(kendo.attr(DIR), descriptor.dir);
                    }
                }

                dir = element.attr(kendo.attr(DIR));

                element.find(".k-i-arrow-n,.k-i-arrow-s").remove();

                if (dir === ASC) {
                    $('<span class="k-icon k-i-arrow-n" />').appendTo(that.link);
                    element.attr(ARIASORT, "ascending");
                } else if (dir === DESC) {
                    $('<span class="k-icon k-i-arrow-s" />').appendTo(that.link);
                    element.attr(ARIASORT, "descending");
                }
            },

            _click: function (e) {
                var that = this,
                element = that.element,
                field = element.attr(kendo.attr(FIELD)),
                dir = element.attr(kendo.attr(DIR)),
                options = that.options,
                compare = that.options.compare === null ? undefined : that.options.compare,
                sort = that.dataSource.sort() || [],
                idx,
                length;

                e.preventDefault();

                if (options.filter && !element.is(options.filter)) {
                    return;
                }

                if (dir === ASC) {
                    dir = DESC;
                } else if (dir === DESC && options.allowUnsort) {
                    dir = undefined;
                } else {
                    dir = ASC;
                }

                if (options.mode === SINGLE) {
                    sort = [{ field: field, dir: dir, compare: compare}];
                } else if (options.mode === "multiple") {
                    for (idx = 0, length = sort.length; idx < length; idx++) {
                        if (sort[idx].field === field) {
                            sort.splice(idx, 1);
                            break;
                        }
                    }
                    sort.push({ field: field, dir: dir, compare: compare });
                }

                this.dataSource.sort(sort);
            }
        });

        ui.plugin(ColumnSorter);

    })(window.kendo.jQuery);




    /* jshint eqnull: true */
    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        extend = $.extend,
        oldIE = kendo.support.browser.msie && kendo.support.browser.version < 9,
        isFunction = kendo.isFunction,
        isPlainObject = $.isPlainObject,
        inArray = $.inArray,
        nameSpecialCharRegExp = /("|\%|'|\[|\]|\$|\.|\,|\:|\;|\+|\*|\&|\!|\#|\(|\)|<|>|\=|\?|\@|\^|\{|\}|\~|\/|\||`)/g,
        ERRORTEMPLATE = '<div class="k-widget k-tooltip k-tooltip-validation" style="margin:0.5em"><span class="k-icon k-warning"> </span>' +
                    '#=message#<div class="k-callout k-callout-n"></div></div>',
        CHANGE = "change";

        var specialRules = ["url", "email", "number", "date", "boolean"];

        function fieldType(field) {
            field = field != null ? field : "";
            return field.type || $.type(field) || "string";
        }

        function convertToValueBinding(container) {
            container.find(":input:not(:button, [" + kendo.attr("role") + "=upload], [" + kendo.attr("skip") + "], [type=file]), select").each(function () {
                var bindAttr = kendo.attr("bind"),
                binding = this.getAttribute(bindAttr) || "",
                bindingName = this.type === "checkbox" || this.type === "radio" ? "checked:" : "value:",
                fieldName = this.name;

                if (binding.indexOf(bindingName) === -1 && fieldName) {
                    binding += (binding.length ? "," : "") + bindingName + fieldName;

                    $(this).attr(bindAttr, binding);
                }
            });
        }

        function createAttributes(options) {
            var field = (options.model.fields || options.model)[options.field],
            type = fieldType(field),
            validation = field ? field.validation : {},
            ruleName,
            DATATYPE = kendo.attr("type"),
            BINDING = kendo.attr("bind"),
            rule,
            attr = {
                name: options.field
            };

            for (ruleName in validation) {
                rule = validation[ruleName];

                if (inArray(ruleName, specialRules) >= 0) {
                    attr[DATATYPE] = ruleName;
                } else if (!isFunction(rule)) {
                    attr[ruleName] = isPlainObject(rule) ? rule.value || ruleName : rule;
                }

                attr[kendo.attr(ruleName + "-msg")] = rule.message;
            }

            if (inArray(type, specialRules) >= 0) {
                attr[DATATYPE] = type;
            }

            attr[BINDING] = (type === "boolean" ? "checked:" : "value:") + options.field;

            return attr;
        }

        function convertItems(items) {
            var idx,
            length,
            item,
            value,
            text,
            result;

            if (items && items.length) {
                result = [];
                for (idx = 0, length = items.length; idx < length; idx++) {
                    item = items[idx];
                    text = item.text || item.value || item;
                    value = item.value == null ? (item.text || item) : item.value;

                    result[idx] = { text: text, value: value };
                }
            }
            return result;
        }

        var editors = {
            "number": function (container, options) {
                var attr = createAttributes(options);
                $('<input type="text"/>').attr(attr).appendTo(container).kendoNumericTextBox({ format: options.format });
                $('<span ' + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
            },
            "date": function (container, options) {
                var attr = createAttributes(options),
                format = options.format;

                if (format) {
                    format = kendo._extractFormat(format);
                }

                attr[kendo.attr("format")] = format;

                $('<input type="text"/>').attr(attr).appendTo(container).kendoDatePicker({ format: options.format });
                $('<span ' + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
            },
            "string": function (container, options) {
                var attr = createAttributes(options);

                $('<input type="text" class="k-input k-textbox"/>').attr(attr).appendTo(container);
            },
            "boolean": function (container, options) {
                var attr = createAttributes(options);
                $('<input type="checkbox" />').attr(attr).appendTo(container);
            },
            "values": function (container, options) {
                var attr = createAttributes(options);
                $('<select ' + kendo.attr("text-field") + '="text"' + kendo.attr("value-field") + '="value"' +
                kendo.attr("source") + "=\'" + kendo.stringify(convertItems(options.values)).replace(/\'/g, "&apos;") +
                "\'" + kendo.attr("role") + '="dropdownlist"/>').attr(attr).appendTo(container);
                $('<span ' + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
            }
        };

        function addValidationRules(modelField, rules) {
            var validation = modelField ? (modelField.validation || {}) : {},
            rule,
            descriptor;

            for (rule in validation) {
                descriptor = validation[rule];

                if (isPlainObject(descriptor) && descriptor.value) {
                    descriptor = descriptor.value;
                }

                if (isFunction(descriptor)) {
                    rules[rule] = descriptor;
                }
            }
        }

        var Editable = Widget.extend({
            init: function (element, options) {
                var that = this;

                if (options.target) {
                    options.$angular = options.target.options.$angular;
                }
                Widget.fn.init.call(that, element, options);
                that._validateProxy = $.proxy(that._validate, that);
                that.refresh();
            },

            events: [CHANGE],

            options: {
                name: "Editable",
                editors: editors,
                clearContainer: true,
                errorTemplate: ERRORTEMPLATE
            },

            editor: function (field, modelField) {
                var that = this,
                editors = that.options.editors,
                isObject = isPlainObject(field),
                fieldName = isObject ? field.field : field,
                model = that.options.model || {},
                isValuesEditor = isObject && field.values,
                type = isValuesEditor ? "values" : fieldType(modelField),
                isCustomEditor = isObject && field.editor,
                editor = isCustomEditor ? field.editor : editors[type],
                container = that.element.find("[" + kendo.attr("container-for") + "=" + fieldName.replace(nameSpecialCharRegExp, "\\$1") + "]");

                editor = editor ? editor : editors.string;

                if (isCustomEditor && typeof field.editor === "string") {
                    editor = function (container) {
                        container.append(field.editor);
                    };
                }

                container = container.length ? container : that.element;
                editor(container, extend(true, {}, isObject ? field : { field: fieldName }, { model: model }));
            },

            _validate: function (e) {
                var that = this,
                input,
                value = e.value,
                preventChangeTrigger = that._validationEventInProgress,
                values = {},
                bindAttribute = kendo.attr("bind"),
                fieldName = e.field.replace(nameSpecialCharRegExp, "\\$1"),
                checkedBinding = 'checked:' + fieldName,
                valueBinding = 'value:' + fieldName;

                values[e.field] = e.value;

                input = $(':input[' + bindAttribute + '*="' + valueBinding + '"],:input[' + bindAttribute + '*="' + checkedBinding + '"]', that.element)
                .filter("[" + kendo.attr("validate") + "!='false']");
                if (input.length > 1) {
                    input = input.filter(function () {
                        var element = $(this);
                        var bindings = element.attr(bindAttribute).split(",");
                        var matchesBinding = inArray(valueBinding, bindings) >= 0 || inArray(checkedBinding, bindings) >= 0;
                        return matchesBinding && (!element.is(":radio") || element.val() == value);
                    });
                }

                try {
                    that._validationEventInProgress = true;

                    if (!that.validatable.validateInput(input) || (!preventChangeTrigger && that.trigger(CHANGE, { values: values }))) {
                        e.preventDefault();
                    }

                } finally {
                    that._validationEventInProgress = false;
                }
            },

            end: function () {
                return this.validatable.validate();
            },

            destroy: function () {
                var that = this;

                that.angular("cleanup", function () {
                    return { elements: that.element };
                });

                Widget.fn.destroy.call(that);

                that.options.model.unbind("set", that._validateProxy);

                kendo.unbind(that.element);

                if (that.validatable) {
                    that.validatable.destroy();
                }
                kendo.destroy(that.element);

                that.element.removeData("kendoValidator");
            },

            refresh: function () {
                var that = this,
                idx,
                length,
                fields = that.options.fields || [],
                container = that.options.clearContainer ? that.element.empty() : that.element,
                model = that.options.model || {},
                rules = {},
                field,
                isObject,
                fieldName,
                modelField,
                modelFields;

                if (!$.isArray(fields)) {
                    fields = [fields];
                }

                for (idx = 0, length = fields.length; idx < length; idx++) {
                    field = fields[idx];
                    isObject = isPlainObject(field);
                    fieldName = isObject ? field.field : field;
                    modelField = (model.fields || model)[fieldName];

                    addValidationRules(modelField, rules);

                    that.editor(field, modelField);
                }

                if (that.options.target) {
                    that.angular("compile", function () {
                        return {
                            elements: container,
                            data: [{ dataItem: model}]
                        };
                    });
                }

                if (!length) {
                    modelFields = model.fields || model;
                    for (fieldName in modelFields) {
                        addValidationRules(modelFields[fieldName], rules);
                    }
                }

                convertToValueBinding(container);

                if (that.validatable) {
                    that.validatable.destroy();
                }

                kendo.bind(container, that.options.model);

                that.options.model.unbind("set", that._validateProxy);
                that.options.model.bind("set", that._validateProxy);

                that.validatable = new kendo.ui.Validator(container, {
                    validateOnBlur: false,
                    errorTemplate: that.options.errorTemplate || undefined,
                    rules: rules
                });

                var focusable = container.find(":kendoFocusable").eq(0).focus();
                if (oldIE) {
                    focusable.focus();
                }
            }
        });

        ui.plugin(Editable);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        Draggable = kendo.ui.Draggable,
        isPlainObject = $.isPlainObject,
        activeElement = kendo._activeElement,
        proxy = $.proxy,
        extend = $.extend,
        each = $.each,
        template = kendo.template,
        BODY = "body",
        templates,
        NS = ".kendoWindow",
        // classNames
        KWINDOW = ".k-window",
        KWINDOWTITLE = ".k-window-title",
        KWINDOWTITLEBAR = KWINDOWTITLE + "bar",
        KWINDOWCONTENT = ".k-window-content",
        KWINDOWRESIZEHANDLES = ".k-resize-handle",
        KOVERLAY = ".k-overlay",
        KCONTENTFRAME = "k-content-frame",
        LOADING = "k-loading",
        KHOVERSTATE = "k-state-hover",
        KFOCUSEDSTATE = "k-state-focused",
        MAXIMIZEDSTATE = "k-window-maximized",
        // constants
        VISIBLE = ":visible",
        HIDDEN = "hidden",
        CURSOR = "cursor",
        // events
        OPEN = "open",
        ACTIVATE = "activate",
        DEACTIVATE = "deactivate",
        CLOSE = "close",
        REFRESH = "refresh",
        RESIZE = "resize",
        RESIZEEND = "resizeEnd",
        DRAGSTART = "dragstart",
        DRAGEND = "dragend",
        ERROR = "error",
        OVERFLOW = "overflow",
        ZINDEX = "zIndex",
        MINIMIZE_MAXIMIZE = ".k-window-actions .k-i-minimize,.k-window-actions .k-i-maximize",
        KPIN = ".k-i-pin",
        KUNPIN = ".k-i-unpin",
        PIN_UNPIN = KPIN + "," + KUNPIN,
        TITLEBAR_BUTTONS = ".k-window-titlebar .k-window-action",
        REFRESHICON = ".k-window-titlebar .k-i-refresh",
        isLocalUrl = kendo.isLocalUrl;

        function defined(x) {
            return (typeof x != "undefined");
        }

        function constrain(value, low, high) {
            return Math.max(Math.min(parseInt(value, 10), high === Infinity ? high : parseInt(high, 10)), parseInt(low, 10));
        }

        function sizingAction(actionId, callback) {
            return function () {
                var that = this,
                wrapper = that.wrapper,
                style = wrapper[0].style,
                options = that.options;

                if (options.isMaximized || options.isMinimized) {
                    return;
                }

                that.restoreOptions = {
                    width: style.width,
                    height: style.height
                };

                wrapper
                .children(KWINDOWRESIZEHANDLES).hide().end()
                .children(KWINDOWTITLEBAR).find(MINIMIZE_MAXIMIZE).parent().hide()
                    .eq(0).before(templates.action({ name: "Restore" }));

                callback.call(that);

                if (actionId == "maximize") {
                    that.wrapper.children(KWINDOWTITLEBAR).find(PIN_UNPIN).parent().hide();
                } else {
                    that.wrapper.children(KWINDOWTITLEBAR).find(PIN_UNPIN).parent().show();
                }

                return that;
            };
        }

        function executableScript() {
            return !this.type || this.type.toLowerCase().indexOf("script") >= 0;
        }

        var Window = Widget.extend({
            init: function (element, options) {
                var that = this,
                wrapper,
                offset = {},
                visibility, display, position,
                isVisible = false,
                content,
                windowContent,
                suppressActions = options && options.actions && !options.actions.length,
                id;

                Widget.fn.init.call(that, element, options);
                options = that.options;
                position = options.position;
                element = that.element;
                content = options.content;

                if (suppressActions) {
                    options.actions = [];
                }

                that.appendTo = $(options.appendTo);

                that._animations();

                if (content && !isPlainObject(content)) {
                    content = options.content = { url: content };
                }

                // remove script blocks to prevent double-execution
                element.find("script").filter(executableScript).remove();

                if (!element.parent().is(that.appendTo) && (position.top === undefined || position.left === undefined)) {
                    if (element.is(VISIBLE)) {
                        offset = element.offset();
                        isVisible = true;
                    } else {
                        visibility = element.css("visibility");
                        display = element.css("display");

                        element.css({ visibility: HIDDEN, display: "" });
                        offset = element.offset();
                        element.css({ visibility: visibility, display: display });
                    }

                    if (position.top === undefined) {
                        position.top = offset.top;
                    }
                    if (position.left === undefined) {
                        position.left = offset.left;
                    }
                }

                if (!defined(options.visible) || options.visible === null) {
                    options.visible = element.is(VISIBLE);
                }

                wrapper = that.wrapper = element.closest(KWINDOW);

                if (!element.is(".k-content") || !wrapper[0]) {
                    element.addClass("k-window-content k-content");
                    that._createWindow(element, options);
                    wrapper = that.wrapper = element.closest(KWINDOW);

                    that._dimensions();
                }

                that._position();

                if (options.pinned) {
                    that.pin(true);
                }

                if (content) {
                    that.refresh(content);
                }

                if (options.visible) {
                    that.toFront();
                }

                windowContent = wrapper.children(KWINDOWCONTENT);
                that._tabindex(windowContent);

                if (options.visible && options.modal) {
                    that._overlay(wrapper.is(VISIBLE)).css({ opacity: 0.5 });
                }

                wrapper
                .on("mouseenter" + NS, TITLEBAR_BUTTONS, proxy(that._buttonEnter, that))
                .on("mouseleave" + NS, TITLEBAR_BUTTONS, proxy(that._buttonLeave, that))
                .on("click" + NS, "> " + TITLEBAR_BUTTONS, proxy(that._windowActionHandler, that));

                windowContent
                .on("keydown" + NS, proxy(that._keydown, that))
                .on("focus" + NS, proxy(that._focus, that))
                .on("blur" + NS, proxy(that._blur, that));

                this._resizable();

                this._draggable();

                id = element.attr("id");
                if (id) {
                    id = id + "_wnd_title";
                    wrapper.children(KWINDOWTITLEBAR)
                       .children(KWINDOWTITLE)
                       .attr("id", id);

                    windowContent
                    .attr({
                        "role": "dialog",
                        "aria-labelledby": id
                    });
                }

                wrapper.add(wrapper.children(".k-resize-handle," + KWINDOWTITLEBAR))
                    .on("mousedown" + NS, proxy(that.toFront, that));

                that.touchScroller = kendo.touchScroller(element);

                that._resizeHandler = proxy(that._onDocumentResize, that);

                that._marker = kendo.guid().substring(0, 8);

                $(window).on("resize" + NS + that._marker, that._resizeHandler);

                if (options.visible) {
                    that.trigger(OPEN);
                    that.trigger(ACTIVATE);
                }

                kendo.notify(that);
            },

            _buttonEnter: function (e) {
                $(e.currentTarget).addClass(KHOVERSTATE);
            },

            _buttonLeave: function (e) {
                $(e.currentTarget).removeClass(KHOVERSTATE);
            },

            _focus: function () {
                this.wrapper.addClass(KFOCUSEDSTATE);
            },

            _blur: function () {
                this.wrapper.removeClass(KFOCUSEDSTATE);
            },

            _dimensions: function () {
                var wrapper = this.wrapper;
                var options = this.options;
                var width = options.width;
                var height = options.height;
                var maxHeight = options.maxHeight;
                var dimensions = ["minWidth", "minHeight", "maxWidth", "maxHeight"];

                this.title(options.title);

                for (var i = 0; i < dimensions.length; i++) {
                    var value = options[dimensions[i]];
                    if (value && value != Infinity) {
                        wrapper.css(dimensions[i], value);
                    }
                }

                if (maxHeight && maxHeight != Infinity) {
                    this.element.css("maxHeight", maxHeight);
                }

                if (width) {
                    if (width.toString().indexOf("%") > 0) {
                        wrapper.width(width);
                    } else {
                        wrapper.width(constrain(width, options.minWidth, options.maxWidth));
                    }
                }

                if (height) {
                    if (height.toString().indexOf("%") > 0) {
                        wrapper.height(height);
                    } else {
                        wrapper.height(constrain(height, options.minHeight, options.maxHeight));
                    }
                }

                if (!options.visible) {
                    wrapper.hide();
                }
            },

            _position: function () {
                var wrapper = this.wrapper,
                position = this.options.position;

                if (position.top === 0) {
                    position.top = position.top.toString();
                }

                if (position.left === 0) {
                    position.left = position.left.toString();
                }

                wrapper.css({
                    top: position.top || "",
                    left: position.left || ""
                });
            },

            _animations: function () {
                var options = this.options;

                if (options.animation === false) {
                    options.animation = { open: { effects: {} }, close: { hide: true, effects: {}} };
                }
            },

            _resize: function () {
                kendo.resize(this.element.children());
            },

            _resizable: function () {
                var resizable = this.options.resizable;
                var wrapper = this.wrapper;

                if (this.resizing) {
                    wrapper
                    .off("dblclick" + NS)
                    .children(KWINDOWRESIZEHANDLES).remove();

                    this.resizing.destroy();
                    this.resizing = null;
                }

                if (resizable) {
                    wrapper.on("dblclick" + NS, KWINDOWTITLEBAR, proxy(function (e) {
                        if (!$(e.target).closest(".k-window-action").length) {
                            this.toggleMaximization();
                        }
                    }, this));

                    each("n e s w se sw ne nw".split(" "), function (index, handler) {
                        wrapper.append(templates.resizeHandle(handler));
                    });

                    this.resizing = new WindowResizing(this);
                }

                wrapper = null;
            },

            _draggable: function () {
                var draggable = this.options.draggable;

                if (this.dragging) {
                    this.dragging.destroy();
                    this.dragging = null;
                }
                if (draggable) {
                    this.dragging = new WindowDragging(this, draggable.dragHandle || KWINDOWTITLEBAR);
                }
            },

            _actions: function () {
                var actions = this.options.actions;
                var titlebar = this.wrapper.children(KWINDOWTITLEBAR);
                var container = titlebar.find(".k-window-actions");

                actions = $.map(actions, function (action) {
                    return { name: action };
                });

                container.html(kendo.render(templates.action, actions));
            },

            setOptions: function (options) {
                Widget.fn.setOptions.call(this, options);
                this._animations();
                this._dimensions();
                this._position();
                this._resizable();
                this._draggable();
                this._actions();
            },

            events: [
            OPEN,
            ACTIVATE,
            DEACTIVATE,
            CLOSE,
            REFRESH,
            RESIZE,
            RESIZEEND,
            DRAGSTART,
            DRAGEND,
            ERROR
        ],

            options: {
                name: "Window",
                animation: {
                    open: {
                        effects: { zoom: { direction: "in" }, fade: { direction: "in"} },
                        duration: 350
                    },
                    close: {
                        effects: { zoom: { direction: "out", properties: { scale: 0.7} }, fade: { direction: "out"} },
                        duration: 350,
                        hide: true
                    }
                },
                title: "",
                actions: ["Close"],
                autoFocus: true,
                modal: false,
                resizable: true,
                draggable: true,
                minWidth: 90,
                minHeight: 50,
                maxWidth: Infinity,
                maxHeight: Infinity,
                pinned: false,
                position: {},
                content: null,
                visible: null,
                height: null,
                width: null,
                appendTo: "body"
            },

            _closable: function () {
                return $.inArray("close", $.map(this.options.actions, function (x) { return x.toLowerCase(); })) > -1;
            },

            _keydown: function (e) {
                var that = this,
                options = that.options,
                keys = kendo.keys,
                keyCode = e.keyCode,
                wrapper = that.wrapper,
                offset, handled,
                distance = 10,
                isMaximized = that.options.isMaximized,
                newWidth, newHeight, w, h;

                if (e.target != e.currentTarget || that._closing) {
                    return;
                }

                if (keyCode == keys.ESC && that._closable()) {
                    that._close(false);
                }

                if (options.draggable && !e.ctrlKey && !isMaximized) {
                    offset = kendo.getOffset(wrapper);

                    if (keyCode == keys.UP) {
                        handled = wrapper.css("top", offset.top - distance);
                    } else if (keyCode == keys.DOWN) {
                        handled = wrapper.css("top", offset.top + distance);
                    } else if (keyCode == keys.LEFT) {
                        handled = wrapper.css("left", offset.left - distance);
                    } else if (keyCode == keys.RIGHT) {
                        handled = wrapper.css("left", offset.left + distance);
                    }
                }

                if (options.resizable && e.ctrlKey && !isMaximized) {
                    if (keyCode == keys.UP) {
                        handled = true;
                        newHeight = wrapper.height() - distance;
                    } else if (keyCode == keys.DOWN) {
                        handled = true;
                        newHeight = wrapper.height() + distance;
                    } if (keyCode == keys.LEFT) {
                        handled = true;
                        newWidth = wrapper.width() - distance;
                    } else if (keyCode == keys.RIGHT) {
                        handled = true;
                        newWidth = wrapper.width() + distance;
                    }

                    if (handled) {
                        w = constrain(newWidth, options.minWidth, options.maxWidth);
                        h = constrain(newHeight, options.minHeight, options.maxHeight);

                        if (!isNaN(w)) {
                            wrapper.width(w);
                            that.options.width = w + "px";
                        }
                        if (!isNaN(h)) {
                            wrapper.height(h);
                            that.options.height = h + "px";
                        }

                        that.resize();
                    }
                }

                if (handled) {
                    e.preventDefault();
                }
            },

            _overlay: function (visible) {
                var overlay = this.appendTo.children(KOVERLAY),
                wrapper = this.wrapper;

                if (!overlay.length) {
                    overlay = $("<div class='k-overlay' />");
                }

                overlay
                .insertBefore(wrapper[0])
                .toggle(visible)
                .css(ZINDEX, parseInt(wrapper.css(ZINDEX), 10) - 1);

                return overlay;
            },

            _actionForIcon: function (icon) {
                var iconClass = /\bk-i-\w+\b/.exec(icon[0].className)[0];

                return {
                    "k-i-close": "_close",
                    "k-i-maximize": "maximize",
                    "k-i-minimize": "minimize",
                    "k-i-restore": "restore",
                    "k-i-refresh": "refresh",
                    "k-i-pin": "pin",
                    "k-i-unpin": "unpin"
                }[iconClass];
            },

            _windowActionHandler: function (e) {
                if (this._closing) {
                    return;
                }

                var icon = $(e.target).closest(".k-window-action").find(".k-icon");
                var action = this._actionForIcon(icon);

                if (action) {
                    e.preventDefault();
                    this[action]();
                    return false;
                }
            },

            _modals: function () {
                var that = this;

                var zStack = $(KWINDOW).filter(function () {
                    var dom = $(this);
                    var object = that._object(dom);
                    var options = object && object.options;

                    return options && options.modal && options.visible && dom.is(VISIBLE);
                }).sort(function (a, b) {
                    return +$(a).css("zIndex") - +$(b).css("zIndex");
                });

                that = null;

                return zStack;
            },

            _object: function (element) {
                var content = element.children(KWINDOWCONTENT);

                return content.data("kendoWindow") || content.data("kendo" + this.options.name);
            },

            center: function () {
                var that = this,
                position = that.options.position,
                wrapper = that.wrapper,
                documentWindow = $(window),
                scrollTop = 0,
                scrollLeft = 0,
                newTop, newLeft;

                if (that.options.isMaximized) {
                    return that;
                }

                if (!that.options.pinned) {
                    scrollTop = documentWindow.scrollTop();
                    scrollLeft = documentWindow.scrollLeft();
                }

                newLeft = scrollLeft + Math.max(0, (documentWindow.width() - wrapper.width()) / 2);
                newTop = scrollTop + Math.max(0, (documentWindow.height() - wrapper.height() - parseInt(wrapper.css("paddingTop"), 10)) / 2);

                wrapper.css({
                    left: newLeft,
                    top: newTop
                });

                position.top = newTop;
                position.left = newLeft;

                return that;
            },

            title: function (text) {
                var that = this,
                wrapper = that.wrapper,
                options = that.options,
                titleBar = wrapper.children(KWINDOWTITLEBAR),
                title = titleBar.children(KWINDOWTITLE),
                titleBarHeight;

                if (!arguments.length) {
                    return title.text();
                }

                if (text === false) {
                    wrapper.addClass("k-window-titleless");
                    titleBar.remove();
                } else {
                    if (!titleBar.length) {
                        wrapper.prepend(templates.titlebar(options));
                        that._actions();
                        titleBar = wrapper.children(KWINDOWTITLEBAR);
                    } else {
                        title.html(text);
                    }

                    titleBarHeight = titleBar.outerHeight();

                    wrapper.css("padding-top", titleBarHeight);
                    titleBar.css("margin-top", -titleBarHeight);
                }

                that.options.title = text;

                return that;
            },

            content: function (html, data) {
                var content = this.wrapper.children(KWINDOWCONTENT),
                scrollContainer = content.children(".km-scroll-container");

                content = scrollContainer[0] ? scrollContainer : content;

                if (!defined(html)) {
                    return content.html();
                }

                this.angular("cleanup", function () {
                    return { elements: content.children() };
                });

                kendo.destroy(this.element.children());

                content.empty().html(html);

                this.angular("compile", function () {
                    var a = [];
                    for (var i = content.length; --i >= 0; ) {
                        a.push({ dataItem: data });
                    }
                    return {
                        elements: content.children(),
                        data: a
                    };
                });

                return this;
            },

            open: function () {
                var that = this,
                wrapper = that.wrapper,
                options = that.options,
                showOptions = options.animation.open,
                contentElement = wrapper.children(KWINDOWCONTENT),
                overlay;

                if (!that.trigger(OPEN)) {
                    if (that._closing) {
                        wrapper.kendoStop(true, true);
                    }

                    that._closing = false;

                    that.toFront();

                    if (options.autoFocus) {
                        that.element.focus();
                    }

                    options.visible = true;

                    if (options.modal) {
                        overlay = that._overlay(false);

                        overlay.kendoStop(true, true);

                        if (showOptions.duration && kendo.effects.Fade) {
                            var overlayFx = kendo.fx(overlay).fadeIn();
                            overlayFx.duration(showOptions.duration || 0);
                            overlayFx.endValue(0.5);
                            overlayFx.play();
                        } else {
                            overlay.css("opacity", 0.5);
                        }

                        overlay.show();
                    }

                    if (!wrapper.is(VISIBLE)) {
                        contentElement.css(OVERFLOW, HIDDEN);
                        wrapper.show().kendoStop().kendoAnimate({
                            effects: showOptions.effects,
                            duration: showOptions.duration,
                            complete: proxy(this._activate, this)
                        });
                    }
                }

                if (options.isMaximized) {
                    that._documentScrollTop = $(document).scrollTop();
                    $("html, body").css(OVERFLOW, HIDDEN);
                }

                return that;
            },

            _activate: function () {
                if (this.options.autoFocus) {
                    this.element.focus();
                }
                this.trigger(ACTIVATE);
                this.wrapper.children(KWINDOWCONTENT).css(OVERFLOW, "");
            },

            _removeOverlay: function (suppressAnimation) {
                var modals = this._modals();
                var options = this.options;
                var hideOverlay = options.modal && !modals.length;
                var overlay = options.modal ? this._overlay(true) : $(undefined);
                var hideOptions = options.animation.close;

                if (hideOverlay) {
                    if (!suppressAnimation && hideOptions.duration && kendo.effects.Fade) {
                        var overlayFx = kendo.fx(overlay).fadeOut();
                        overlayFx.duration(hideOptions.duration || 0);
                        overlayFx.startValue(0.5);
                        overlayFx.play();
                    } else {
                        this._overlay(false).remove();
                    }
                } else if (modals.length) {
                    this._object(modals.last())._overlay(true);
                }
            },

            _close: function (systemTriggered) {
                var that = this,
                wrapper = that.wrapper,
                options = that.options,
                showOptions = options.animation.open,
                hideOptions = options.animation.close;

                if (wrapper.is(VISIBLE) && !that.trigger(CLOSE, { userTriggered: !systemTriggered })) {
                    if (that._closing) {
                        return;
                    }

                    that._closing = true;
                    options.visible = false;

                    $(KWINDOW).each(function (i, element) {
                        var contentElement = $(element).children(KWINDOWCONTENT);

                        // Remove overlay set by toFront
                        if (element != wrapper && contentElement.find("> ." + KCONTENTFRAME).length > 0) {
                            contentElement.children(KOVERLAY).remove();
                        }
                    });

                    this._removeOverlay();

                    wrapper.kendoStop().kendoAnimate({
                        effects: hideOptions.effects || showOptions.effects,
                        reverse: hideOptions.reverse === true,
                        duration: hideOptions.duration,
                        complete: proxy(this._deactivate, this)
                    });
                }

                if (that.options.isMaximized) {
                    $("html, body").css(OVERFLOW, "");
                    if (that._documentScrollTop && that._documentScrollTop > 0) {
                        $(document).scrollTop(that._documentScrollTop);
                    }
                }
            },

            _deactivate: function () {
                this.wrapper.hide().css("opacity", "");
                this.trigger(DEACTIVATE);
                var lastModal = this._object(this._modals().last());
                if (lastModal) {
                    lastModal.toFront();
                }
            },

            close: function () {
                this._close(true);
                return this;
            },

            _actionable: function (element) {
                return $(element).is(TITLEBAR_BUTTONS + "," + TITLEBAR_BUTTONS + " .k-icon,:input,a");
            },

            _shouldFocus: function (target) {
                var active = activeElement(),
                element = this.element;

                return this.options.autoFocus &&
                    !$(active).is(element) &&
                    !this._actionable(target) &&
                    (!element.find(active).length || !element.find(target).length);
            },

            toFront: function (e) {
                var that = this,
                wrapper = that.wrapper,
                currentWindow = wrapper[0],
                zIndex = +wrapper.css(ZINDEX),
                originalZIndex = zIndex,
                target = (e && e.target) || null;

                $(KWINDOW).each(function (i, element) {
                    var windowObject = $(element),
                    zIndexNew = windowObject.css(ZINDEX),
                    contentElement = windowObject.children(KWINDOWCONTENT);

                    if (!isNaN(zIndexNew)) {
                        zIndex = Math.max(+zIndexNew, zIndex);
                    }

                    // Add overlay to windows with iframes and lower z-index to prevent
                    // trapping of events when resizing / dragging
                    if (element != currentWindow && contentElement.find("> ." + KCONTENTFRAME).length > 0) {
                        contentElement.append(templates.overlay);
                    }
                });

                if (!wrapper[0].style.zIndex || originalZIndex < zIndex) {
                    wrapper.css(ZINDEX, zIndex + 2);
                }
                that.element.find("> .k-overlay").remove();

                if (that._shouldFocus(target)) {
                    that.element.focus();

                    var scrollTop = $(window).scrollTop(),
                    windowTop = parseInt(wrapper.position().top, 10);

                    if (windowTop > 0 && windowTop < scrollTop) {
                        if (scrollTop > 0) {
                            $(window).scrollTop(windowTop);
                        } else {
                            wrapper.css("top", scrollTop);
                        }
                    }
                }

                wrapper = null;

                return that;
            },

            toggleMaximization: function () {
                if (this._closing) {
                    return this;
                }

                return this[this.options.isMaximized ? "restore" : "maximize"]();
            },

            restore: function () {
                var that = this;
                var options = that.options;
                var minHeight = options.minHeight;
                var restoreOptions = that.restoreOptions;

                if (!options.isMaximized && !options.isMinimized) {
                    return that;
                }

                if (minHeight && minHeight != Infinity) {
                    that.wrapper.css("min-height", minHeight);
                }

                that.wrapper
                .css({
                    position: options.pinned ? "fixed" : "absolute",
                    left: restoreOptions.left,
                    top: restoreOptions.top,
                    width: restoreOptions.width,
                    height: restoreOptions.height
                })
                .removeClass(MAXIMIZEDSTATE)
                .find(".k-window-content,.k-resize-handle").show().end()
                .find(".k-window-titlebar .k-i-restore").parent().remove().end().end()
                .find(MINIMIZE_MAXIMIZE).parent().show().end().end()
                .find(PIN_UNPIN).parent().show();

                that.options.width = restoreOptions.width;
                that.options.height = restoreOptions.height;

                $("html, body").css(OVERFLOW, "");
                if (this._documentScrollTop && this._documentScrollTop > 0) {
                    $(document).scrollTop(this._documentScrollTop);
                }

                options.isMaximized = options.isMinimized = false;

                that.resize();

                return that;
            },

            maximize: sizingAction("maximize", function () {
                var that = this,
                wrapper = that.wrapper,
                position = wrapper.position();

                extend(that.restoreOptions, {
                    left: position.left,
                    top: position.top
                });

                wrapper.css({
                    left: 0,
                    top: 0,
                    position: "fixed"
                })
                .addClass(MAXIMIZEDSTATE);

                this._documentScrollTop = $(document).scrollTop();
                $("html, body").css(OVERFLOW, HIDDEN);

                that.options.isMaximized = true;

                that._onDocumentResize();
            }),

            minimize: sizingAction("minimize", function () {
                var that = this;

                that.wrapper.css({
                    height: "",
                    minHeight: ""
                });

                that.element.hide();

                that.options.isMinimized = true;
            }),

            pin: function (force) {
                var that = this,
                win = $(window),
                wrapper = that.wrapper,
                top = parseInt(wrapper.css("top"), 10),
                left = parseInt(wrapper.css("left"), 10);

                if (force || !that.options.pinned && !that.options.isMaximized) {
                    wrapper.css({ position: "fixed", top: top - win.scrollTop(), left: left - win.scrollLeft() });
                    wrapper.children(KWINDOWTITLEBAR).find(KPIN).addClass("k-i-unpin").removeClass("k-i-pin");

                    that.options.pinned = true;
                }
            },

            unpin: function () {
                var that = this,
                win = $(window),
                wrapper = that.wrapper,
                top = parseInt(wrapper.css("top"), 10),
                left = parseInt(wrapper.css("left"), 10);

                if (that.options.pinned && !that.options.isMaximized) {
                    wrapper.css({ position: "", top: top + win.scrollTop(), left: left + win.scrollLeft() });
                    wrapper.children(KWINDOWTITLEBAR).find(KUNPIN).addClass("k-i-pin").removeClass("k-i-unpin");

                    that.options.pinned = false;
                }
            },

            _onDocumentResize: function () {
                var that = this,
                wrapper = that.wrapper,
                wnd = $(window),
                w, h;

                if (!that.options.isMaximized) {
                    return;
                }

                w = wnd.width();
                h = wnd.height() - parseInt(wrapper.css("padding-top"), 10);

                wrapper.css({
                    width: w,
                    height: h
                });
                that.options.width = w;
                that.options.height = h;

                that.resize();
            },

            refresh: function (options) {
                var that = this,
                initOptions = that.options,
                element = $(that.element),
                iframe,
                showIframe,
                url;

                if (!isPlainObject(options)) {
                    options = { url: options };
                }

                options = extend({}, initOptions.content, options);

                showIframe = defined(initOptions.iframe) ? initOptions.iframe : options.iframe;

                url = options.url;

                if (url) {
                    if (!defined(showIframe)) {
                        showIframe = !isLocalUrl(url);
                    }

                    if (!showIframe) {
                        // perform AJAX request
                        that._ajaxRequest(options);
                    } else {
                        iframe = element.find("." + KCONTENTFRAME)[0];

                        if (iframe) {
                            // refresh existing iframe
                            iframe.src = url || iframe.src;
                        } else {
                            // render new iframe
                            element.html(templates.contentFrame(extend({}, initOptions, { content: options })));
                        }

                        element.find("." + KCONTENTFRAME)
                        .unbind("load" + NS)
                        .on("load" + NS, proxy(this._triggerRefresh, this));
                    }
                } else {
                    if (options.template) {
                        // refresh template
                        that.content(template(options.template)({}));
                    }

                    that.trigger(REFRESH);
                }

                element.toggleClass("k-window-iframecontent", !!showIframe);

                return that;
            },

            _triggerRefresh: function () {
                this.trigger(REFRESH);
            },

            _ajaxComplete: function () {
                clearTimeout(this._loadingIconTimeout);
                this.wrapper.find(REFRESHICON).removeClass(LOADING);
            },

            _ajaxError: function (xhr, status) {
                this.trigger(ERROR, { status: status, xhr: xhr });
            },

            _ajaxSuccess: function (contentTemplate) {
                return function (data) {
                    var html = data;
                    if (contentTemplate) {
                        html = template(contentTemplate)(data || {});
                    }

                    this.content(html, data);
                    this.element.prop("scrollTop", 0);

                    this.trigger(REFRESH);
                };
            },

            _showLoading: function () {
                this.wrapper.find(REFRESHICON).addClass(LOADING);
            },

            _ajaxRequest: function (options) {
                this._loadingIconTimeout = setTimeout(proxy(this._showLoading, this), 100);

                $.ajax(extend({
                    type: "GET",
                    dataType: "html",
                    cache: false,
                    error: proxy(this._ajaxError, this),
                    complete: proxy(this._ajaxComplete, this),
                    success: proxy(this._ajaxSuccess(options.template), this)
                }, options));
            },

            destroy: function () {
                var that = this;

                if (that.resizing) {
                    that.resizing.destroy();
                }

                if (that.dragging) {
                    that.dragging.destroy();
                }

                that.wrapper.off(NS)
                .children(KWINDOWCONTENT).off(NS).end()
                .find(".k-resize-handle,.k-window-titlebar").off(NS);

                $(window).off("resize" + NS + that._marker);

                clearTimeout(that._loadingIconTimeout);

                Widget.fn.destroy.call(that);

                that.unbind(undefined);

                kendo.destroy(that.wrapper);

                that._removeOverlay(true);

                that.wrapper.empty().remove();

                that.wrapper = that.appendTo = that.element = $();
            },

            _createWindow: function () {
                var contentHtml = this.element,
                options = this.options,
                iframeSrcAttributes,
                wrapper,
                isRtl = kendo.support.isRtl(contentHtml);

                if (options.scrollable === false) {
                    contentHtml.attr("style", "overflow:hidden;");
                }

                wrapper = $(templates.wrapper(options));

                // Collect the src attributes of all iframes and then set them to empty string.
                // This seems to fix this IE9 "feature": http://msdn.microsoft.com/en-us/library/gg622929%28v=VS.85%29.aspx?ppud=4
                iframeSrcAttributes = contentHtml.find("iframe:not(.k-content)").map(function () {
                    var src = this.getAttribute("src");
                    this.src = "";
                    return src;
                });

                // Make sure the wrapper is appended to the body only once. IE9+ will throw exceptions if you move iframes in DOM
                wrapper
                .toggleClass("k-rtl", isRtl)
                .appendTo(this.appendTo)
                .append(contentHtml)
                .find("iframe:not(.k-content)").each(function (index) {
                    // Restore the src attribute of the iframes when they are part of the live DOM tree
                    this.src = iframeSrcAttributes[index];
                });

                wrapper.find(".k-window-title")
                .css(isRtl ? "left" : "right", wrapper.find(".k-window-actions").outerWidth() + 10);

                contentHtml.css("visibility", "").show();

                contentHtml.find("[data-role=editor]").each(function () {
                    var editor = $(this).data("kendoEditor");

                    if (editor) {
                        editor.refresh();
                    }
                });

                wrapper = contentHtml = null;
            }
        });

        templates = {
            wrapper: template("<div class='k-widget k-window' />"),
            action: template(
            "<a role='button' href='\\#' class='k-window-action k-link'>" +
                "<span role='presentation' class='k-icon k-i-#= name.toLowerCase() #'>#= name #</span>" +
            "</a>"
        ),
            titlebar: template(
            "<div class='k-window-titlebar k-header'>&nbsp;" +
                "<span class='k-window-title'>#= title #</span>" +
                "<div class='k-window-actions' />" +
            "</div>"
        ),
            overlay: "<div class='k-overlay' />",
            contentFrame: template(
            "<iframe frameborder='0' title='#= title #' class='" + KCONTENTFRAME + "' " +
                "src='#= content.url #'>" +
                    "This page requires frames in order to show content" +
            "</iframe>"
        ),
            resizeHandle: template("<div class='k-resize-handle k-resize-#= data #'></div>")
        };


        function WindowResizing(wnd) {
            var that = this;
            that.owner = wnd;
            that._draggable = new Draggable(wnd.wrapper, {
                filter: ">" + KWINDOWRESIZEHANDLES,
                group: wnd.wrapper.id + "-resizing",
                dragstart: proxy(that.dragstart, that),
                drag: proxy(that.drag, that),
                dragend: proxy(that.dragend, that)
            });

            that._draggable.userEvents.bind("press", proxy(that.addOverlay, that));
            that._draggable.userEvents.bind("release", proxy(that.removeOverlay, that));
        }

        WindowResizing.prototype = {
            addOverlay: function () {
                this.owner.wrapper.append(templates.overlay);
            },
            removeOverlay: function () {
                this.owner.wrapper.find(KOVERLAY).remove();
            },
            dragstart: function (e) {
                var that = this;
                var wnd = that.owner;
                var wrapper = wnd.wrapper;

                that.elementPadding = parseInt(wrapper.css("padding-top"), 10);
                that.initialPosition = kendo.getOffset(wrapper, "position");

                that.resizeDirection = e.currentTarget.prop("className").replace("k-resize-handle k-resize-", "");

                that.initialSize = {
                    width: wrapper.width(),
                    height: wrapper.height()
                };

                that.containerOffset = kendo.getOffset(wnd.appendTo, "position");

                wrapper
                .children(KWINDOWRESIZEHANDLES).not(e.currentTarget).hide();

                $(BODY).css(CURSOR, e.currentTarget.css(CURSOR));
            },
            drag: function (e) {
                var that = this,
                wnd = that.owner,
                wrapper = wnd.wrapper,
                options = wnd.options,
                direction = that.resizeDirection,
                containerOffset = that.containerOffset,
                initialPosition = that.initialPosition,
                initialSize = that.initialSize,
                newWidth, newHeight,
                windowBottom, windowRight,
                x = Math.max(e.x.location, containerOffset.left),
                y = Math.max(e.y.location, containerOffset.top);

                if (direction.indexOf("e") >= 0) {
                    newWidth = x - initialPosition.left;

                    wrapper.width(constrain(newWidth, options.minWidth, options.maxWidth));
                } else if (direction.indexOf("w") >= 0) {
                    windowRight = initialPosition.left + initialSize.width;
                    newWidth = constrain(windowRight - x, options.minWidth, options.maxWidth);

                    wrapper.css({
                        left: windowRight - newWidth - containerOffset.left,
                        width: newWidth
                    });
                }

                if (direction.indexOf("s") >= 0) {
                    newHeight = y - initialPosition.top - that.elementPadding;

                    wrapper.height(constrain(newHeight, options.minHeight, options.maxHeight));
                } else if (direction.indexOf("n") >= 0) {
                    windowBottom = initialPosition.top + initialSize.height;
                    newHeight = constrain(windowBottom - y, options.minHeight, options.maxHeight);

                    wrapper.css({
                        top: windowBottom - newHeight - containerOffset.top,
                        height: newHeight
                    });
                }

                if (newWidth) {
                    wnd.options.width = newWidth + "px";
                }
                if (newHeight) {
                    wnd.options.height = newHeight + "px";
                }

                wnd.resize();
            },
            dragend: function (e) {
                var that = this,
                wnd = that.owner,
                wrapper = wnd.wrapper;

                wrapper
                .children(KWINDOWRESIZEHANDLES).not(e.currentTarget).show();

                $(BODY).css(CURSOR, "");

                if (wnd.touchScroller) {
                    wnd.touchScroller.reset();
                }

                if (e.keyCode == 27) {
                    wrapper.css(that.initialPosition)
                    .css(that.initialSize);
                }

                wnd.trigger(RESIZEEND);

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

                this._draggable = this.owner = null;
            }
        };

        function WindowDragging(wnd, dragHandle) {
            var that = this;
            that.owner = wnd;
            that._draggable = new Draggable(wnd.wrapper, {
                filter: dragHandle,
                group: wnd.wrapper.id + "-moving",
                dragstart: proxy(that.dragstart, that),
                drag: proxy(that.drag, that),
                dragend: proxy(that.dragend, that),
                dragcancel: proxy(that.dragcancel, that)
            });

            that._draggable.userEvents.stopPropagation = false;
        }

        WindowDragging.prototype = {
            dragstart: function (e) {
                var wnd = this.owner,
                element = wnd.element,
                actions = element.find(".k-window-actions"),
                containerOffset = kendo.getOffset(wnd.appendTo);

                wnd.trigger(DRAGSTART);

                wnd.initialWindowPosition = kendo.getOffset(wnd.wrapper, "position");

                wnd.startPosition = {
                    left: e.x.client - wnd.initialWindowPosition.left,
                    top: e.y.client - wnd.initialWindowPosition.top
                };

                if (actions.length > 0) {
                    wnd.minLeftPosition = actions.outerWidth() + parseInt(actions.css("right"), 10) - element.outerWidth();
                } else {
                    wnd.minLeftPosition = 20 - element.outerWidth(); // at least 20px remain visible
                }

                wnd.minLeftPosition -= containerOffset.left;
                wnd.minTopPosition = -containerOffset.top;

                wnd.wrapper
                .append(templates.overlay)
                .children(KWINDOWRESIZEHANDLES).hide();

                $(BODY).css(CURSOR, e.currentTarget.css(CURSOR));
            },

            drag: function (e) {
                var wnd = this.owner,
                position = wnd.options.position,
                newTop = Math.max(e.y.client - wnd.startPosition.top, wnd.minTopPosition),
                newLeft = Math.max(e.x.client - wnd.startPosition.left, wnd.minLeftPosition),
                coordinates = {
                    left: newLeft,
                    top: newTop
                };

                $(wnd.wrapper).css(coordinates);
                position.top = newTop;
                position.left = newLeft;
            },

            _finishDrag: function () {
                var wnd = this.owner;

                wnd.wrapper
                .children(KWINDOWRESIZEHANDLES).toggle(!wnd.options.isMinimized).end()
                .find(KOVERLAY).remove();

                $(BODY).css(CURSOR, "");
            },

            dragcancel: function (e) {
                this._finishDrag();

                e.currentTarget.closest(KWINDOW).css(this.owner.initialWindowPosition);
            },

            dragend: function () {
                this._finishDrag();

                this.owner.trigger(DRAGEND);

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

                this._draggable = this.owner = null;
            }
        };

        kendo.ui.plugin(Window);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        angular = window.angular,
        mobile = kendo.mobile,
        ui = mobile.ui,
        attr = kendo.attr,
        Class = kendo.Class,
        Widget = ui.Widget,
        ViewClone = kendo.ViewClone,
        INIT = "init",
        UI_OVERLAY = '<div style="height: 100%; width: 100%; position: absolute; top: 0; left: 0; z-index: 20000; display: none" />',
        BEFORE_SHOW = "beforeShow",
        SHOW = "show",
        AFTER_SHOW = "afterShow",
        BEFORE_HIDE = "beforeHide",
        TRANSITION_END = "transitionEnd",
        TRANSITION_START = "transitionStart",
        HIDE = "hide",
        DESTROY = "destroy",
        Z_INDEX = "z-index",
        attrValue = kendo.attrValue,
        roleSelector = kendo.roleSelector,
        directiveSelector = kendo.directiveSelector,
        compileMobileDirective = kendo.compileMobileDirective;

        function initPopOvers(element) {
            var popovers = element.find(roleSelector("popover")),
            idx, length,
            roles = ui.roles;

            for (idx = 0, length = popovers.length; idx < length; idx++) {
                kendo.initWidget(popovers[idx], {}, roles);
            }
        }

        function preventScrollIfNotInput(e) {
            if (!kendo.triggeredByInput(e)) {
                e.preventDefault();
            }
        }

        var View = Widget.extend({
            init: function (element, options) {
                Widget.fn.init.call(this, element, options);
                this.params = {};

                $.extend(this, options);

                this.transition = this.transition || this.defaultTransition;

                this._id();

                if (!this.options.$angular) {
                    this._layout();
                    this._overlay();
                    this._scroller();
                    this._model();
                } else {
                    this._overlay();
                }
            },

            events: [
            INIT,
            BEFORE_SHOW,
            SHOW,
            AFTER_SHOW,
            BEFORE_HIDE,
            HIDE,
            DESTROY,
            TRANSITION_START,
            TRANSITION_END
        ],

            options: {
                name: "View",
                title: "",
                layout: null,
                getLayout: $.noop,
                reload: false,
                transition: "",
                defaultTransition: "",
                useNativeScrolling: false,
                stretch: false,
                zoom: false,
                model: null,
                modelScope: window,
                scroller: {},
                initWidgets: true
            },

            enable: function (enable) {
                if (typeof enable == "undefined") {
                    enable = true;
                }

                if (enable) {
                    this.overlay.hide();
                } else {
                    this.overlay.show();
                }
            },

            destroy: function () {
                if (this.layout) {
                    this.layout.detach(this);
                }

                this.trigger(DESTROY);


                Widget.fn.destroy.call(this);

                if (this.scroller) {
                    this.scroller.destroy();
                }

                if (this.options.$angular) {
                    this.element.scope().$destroy();
                }

                kendo.destroy(this.element);
            },

            purge: function () {
                this.destroy();
                this.element.remove();
            },

            triggerBeforeShow: function () {
                if (this.trigger(BEFORE_SHOW, { view: this })) {
                    return false;
                }
                return true;
            },

            showStart: function () {
                var element = this.element;

                element.css("display", "");

                if (!this.inited) {
                    this.inited = true;
                    this.trigger(INIT, { view: this });
                } else { // skip the initial controller update
                    this._invokeNgController();
                }

                if (this.layout) {
                    this.layout.attach(this);
                }

                this._padIfNativeScrolling();
                this.trigger(SHOW, { view: this });
                kendo.resize(element);
            },

            showEnd: function () {
                this.trigger(AFTER_SHOW, { view: this });
                this._padIfNativeScrolling();
            },

            hideStart: function () {
                this.trigger(BEFORE_HIDE, { view: this });
            },

            hideEnd: function () {
                var that = this;
                that.element.hide();
                that.trigger(HIDE, { view: that });

                if (that.layout) {
                    that.layout.trigger(HIDE, { view: that, layout: that.layout });
                }
            },

            beforeTransition: function (type) {
                this.trigger(TRANSITION_START, { type: type });
            },

            afterTransition: function (type) {
                this.trigger(TRANSITION_END, { type: type });
            },

            _padIfNativeScrolling: function () {
                if (mobile.appLevelNativeScrolling()) {
                    var isAndroid = kendo.support.mobileOS && kendo.support.mobileOS.android,
                    isAndroidForced = mobile.application.os.android || (mobile.application.skin().indexOf("android") > -1),
                    topContainer = isAndroid || isAndroidForced ? "footer" : "header",
                    bottomContainer = isAndroid || isAndroidForced ? "header" : "footer";

                    this.content.css({
                        paddingTop: this[topContainer].height(),
                        paddingBottom: this[bottomContainer].height()
                    });
                }
            },

            contentElement: function () {
                var that = this;

                return that.options.stretch ? that.content : that.scrollerContent;
            },

            clone: function (back) {
                return new ViewClone(this);
            },

            _scroller: function () {
                var that = this;

                if (mobile.appLevelNativeScrolling()) {
                    return;
                }
                if (that.options.stretch) {
                    that.content.addClass("km-stretched-view");
                } else {
                    that.content.kendoMobileScroller($.extend(that.options.scroller, { zoom: that.options.zoom, useNative: that.options.useNativeScrolling }));

                    that.scroller = that.content.data("kendoMobileScroller");
                    that.scrollerContent = that.scroller.scrollElement;
                }

                // prevent accidental address bar display when pulling the header
                if (kendo.support.kineticScrollNeeded) {
                    $(that.element).on("touchmove", ".km-header", preventScrollIfNotInput);
                    if (!that.options.useNativeScrolling) {
                        $(that.element).on("touchmove", ".km-content", preventScrollIfNotInput);
                    }
                }
            },

            _model: function () {
                var that = this,
                element = that.element,
                model = that.options.model;

                if (typeof model === "string") {
                    model = kendo.getter(model)(that.options.modelScope);
                }

                that.model = model;

                initPopOvers(element);

                that.element.css("display", "");
                if (that.options.initWidgets) {
                    if (model) {
                        kendo.bind(element, model, ui, kendo.ui, kendo.dataviz.ui);
                    } else {
                        mobile.init(element.children());
                    }
                }
                that.element.css("display", "none");
            },

            _id: function () {
                var element = this.element,
                idAttrValue = element.attr("id") || "";

                this.id = attrValue(element, "url") || "#" + idAttrValue;

                if (this.id == "#") {
                    this.id = kendo.guid();
                    element.attr("id", this.id);
                }
            },

            _layout: function () {
                var contentSelector = roleSelector("content"),
                element = this.element;

                element.addClass("km-view");

                this.header = element.children(roleSelector("header")).addClass("km-header");
                this.footer = element.children(roleSelector("footer")).addClass("km-footer");

                if (!element.children(contentSelector)[0]) {
                    element.wrapInner("<div " + attr("role") + '="content"></div>');
                }

                this.content = element.children(roleSelector("content"))
                                .addClass("km-content");

                this.element.prepend(this.header).append(this.footer);


                this.layout = this.options.getLayout(this.layout);

                if (this.layout) {
                    this.layout.setup(this);
                }
            },

            _overlay: function () {
                this.overlay = $(UI_OVERLAY).appendTo(this.element);
            },

            _invokeNgController: function () {
                var element = this.element,
                controller,
                scope;

                if (this.options.$angular) {
                    controller = element.controller();
                    scope = element.scope();

                    if (controller) {
                        scope.$apply(function () {
                            element.injector().invoke(controller.constructor, null, { $scope: scope });
                        });
                    }
                }
            }
        });

        function initWidgets(collection) {
            collection.each(function () {
                kendo.initWidget($(this), {}, ui.roles);
            });
        }

        var Layout = Widget.extend({
            init: function (element, options) {
                Widget.fn.init.call(this, element, options);

                element = this.element;

                this.header = element.children(this._locate("header")).addClass("km-header");
                this.footer = element.children(this._locate("footer")).addClass("km-footer");
                this.elements = this.header.add(this.footer);

                initPopOvers(element);

                if (!this.options.$angular) {
                    kendo.mobile.init(this.element.children());
                }
                this.element.detach();
                this.trigger(INIT, { layout: this });
            },

            _locate: function (selectors) {
                return this.options.$angular ? directiveSelector(selectors) : roleSelector(selectors);
            },

            options: {
                name: "Layout",
                id: null,
                platform: null
            },

            events: [
            INIT,
            SHOW,
            HIDE
        ],

            setup: function (view) {
                if (!view.header[0]) { view.header = this.header; }
                if (!view.footer[0]) { view.footer = this.footer; }
            },

            detach: function (view) {
                var that = this;
                if (view.header === that.header && that.header[0]) {
                    view.element.prepend(that.header.detach()[0].cloneNode(true));
                }

                if (view.footer === that.footer && that.footer.length) {
                    view.element.append(that.footer.detach()[0].cloneNode(true));
                }
            },

            attach: function (view) {
                var that = this,
                previousView = that.currentView;

                if (previousView) {
                    that.detach(previousView);
                }

                if (view.header === that.header) {
                    that.header.detach();
                    view.element.children(roleSelector("header")).remove();
                    view.element.prepend(that.header);
                }

                if (view.footer === that.footer) {
                    that.footer.detach();
                    view.element.children(roleSelector("footer")).remove();
                    view.element.append(that.footer);
                }

                that.trigger(SHOW, { layout: that, view: view });
                that.currentView = view;
            }
        });

        var Observable = kendo.Observable,
        bodyRegExp = /<body[^>]*>(([\u000a\u000d\u2028\u2029]|.)*)<\/body>/i,
        LOAD_START = "loadStart",
        LOAD_COMPLETE = "loadComplete",
        SHOW_START = "showStart",
        SAME_VIEW_REQUESTED = "sameViewRequested",
        VIEW_SHOW = "viewShow",
        VIEW_TYPE_DETERMINED = "viewTypeDetermined",
        AFTER = "after";

        var ViewEngine = Observable.extend({
            init: function (options) {
                var that = this,
                views,
                errorMessage,
                container,
                collection;

                Observable.fn.init.call(that);

                $.extend(that, options);
                that.sandbox = $("<div />");
                container = that.container;

                views = that._hideViews(container);
                that.rootView = views.first();

                if (!that.rootView[0] && options.rootNeeded) {
                    if (container[0] == kendo.mobile.application.element[0]) {
                        errorMessage = 'Your kendo mobile application element does not contain any direct child elements with data-role="view" attribute set. Make sure that you instantiate the mobile application using the correct container.';
                    } else {
                        errorMessage = 'Your pane element does not contain any direct child elements with data-role="view" attribute set.';
                    }
                    throw new Error(errorMessage);
                }

                that.layouts = {};

                that.viewContainer = new kendo.ViewContainer(that.container);

                that.viewContainer.bind("accepted", function (e) {
                    e.view.params = that.params;
                });

                that.viewContainer.bind("complete", function (e) {
                    that.trigger(VIEW_SHOW, { view: e.view });
                });

                that.viewContainer.bind(AFTER, function (e) {
                    that.trigger(AFTER);
                });

                this.getLayoutProxy = $.proxy(this, "_getLayout");
                that._setupLayouts(container);

                collection = container.children(that._locate("modalview drawer"));
                if (that.$angular) {
                    collection.each(function (idx, element) {
                        compileMobileDirective($(element), function (scope) {
                            //pass the options?
                        });
                    });
                } else {
                    initWidgets(collection);
                }

                this.bind(this.events, options);
            },

            events: [
            SHOW_START,
            AFTER,
            VIEW_SHOW,
            LOAD_START,
            LOAD_COMPLETE,
            SAME_VIEW_REQUESTED,
            VIEW_TYPE_DETERMINED
        ],

            destroy: function () {
                kendo.destroy(this.container);

                for (var id in this.layouts) {
                    this.layouts[id].destroy();
                }
            },

            view: function () {
                return this.viewContainer.view;
            },

            showView: function (url, transition, params) {
                url = url.replace(new RegExp("^" + this.remoteViewURLPrefix), "");
                if (url === "" && this.remoteViewURLPrefix) {
                    url = "/";
                }

                if (url === this.url) {
                    this.trigger(SAME_VIEW_REQUESTED);
                    return false;
                }

                this.trigger(SHOW_START);

                var that = this,
                showClosure = function (view) {
                    return that.viewContainer.show(view, transition, url);
                },
                element = that._findViewElement(url),
                view = kendo.widgetInstance(element);

                that.url = url;
                that.params = params;

                if (view && view.reload) {
                    view.purge();
                    element = [];
                }

                this.trigger(VIEW_TYPE_DETERMINED, { remote: element.length === 0, url: url });

                if (element[0]) {
                    if (!view) {
                        view = that._createView(element);
                    }

                    return showClosure(view);
                } else {
                    if (this.serverNavigation) {
                        location.href = url;
                    } else {
                        that._loadView(url, showClosure);
                    }
                    return true;
                }
            },

            append: function (html, url) {
                var sandbox = this.sandbox,
                urlPath = (url || "").split("?")[0],
                container = this.container,
                views,
                modalViews,
                view;

                if (bodyRegExp.test(html)) {
                    html = RegExp.$1;
                }

                sandbox[0].innerHTML = html;

                container.append(sandbox.children("script, style"));

                views = this._hideViews(sandbox);
                view = views.first();

                // Generic HTML content found as remote view - no remote view markers
                if (!view.length) {
                    views = view = sandbox.wrapInner("<div data-role=view />").children(); // one element
                }

                if (urlPath) {
                    view.hide().attr(attr("url"), urlPath);
                }

                this._setupLayouts(sandbox);

                modalViews = sandbox.children(this._locate("modalview drawer"));

                container.append(sandbox.children(this._locate("layout modalview drawer")).add(views));

                // Initialize the modalviews after they have been appended to the final container
                initWidgets(modalViews);

                return this._createView(view);
            },

            _locate: function (selectors) {
                return this.$angular ? directiveSelector(selectors) : roleSelector(selectors);
            },

            _findViewElement: function (url) {
                var element,
                urlPath = url.split("?")[0];

                if (!urlPath) {
                    return this.rootView;
                }

                element = this.container.children("[" + attr("url") + "='" + urlPath + "']");

                // do not try to search for "#/foo/bar" id, jQuery throws error
                if (!element[0] && urlPath.indexOf("/") === -1) {
                    element = this.container.children(urlPath.charAt(0) === "#" ? urlPath : "#" + urlPath);
                }

                return element;
            },

            _createView: function (element) {
                if (this.$angular) {
                    var that = this;

                    return compileMobileDirective(element, function (scope) {
                        scope.viewOptions = {
                            defaultTransition: that.transition,
                            loader: that.loader,
                            container: that.container,
                            getLayout: that.getLayoutProxy
                        };
                    });
                } else {
                    return kendo.initWidget(element, {
                        defaultTransition: this.transition,
                        loader: this.loader,
                        container: this.container,
                        getLayout: this.getLayoutProxy,
                        modelScope: this.modelScope,
                        reload: attrValue(element, "reload")
                    }, ui.roles);
                }
            },

            _getLayout: function (name) {
                if (name === "") {
                    return null;
                }

                return name ? this.layouts[name] : this.layouts[this.layout];
            },

            _loadView: function (url, callback) {
                if (this._xhr) {
                    this._xhr.abort();
                }

                this.trigger(LOAD_START);

                this._xhr = $.get(kendo.absoluteURL(url, this.remoteViewURLPrefix), "html")
                .always($.proxy(this, "_xhrComplete", callback, url));
            },

            _xhrComplete: function (callback, url, response, status, err) {
                var success = true;

                if (typeof response === "object") {
                    success = response.status === 0 && response.responseText.length > 0;
                    response = response.responseText;
                }

                this.trigger(LOAD_COMPLETE);

                if (success) {
                    callback(this.append(response, url));
                }
            },

            _hideViews: function (container) {
                return container.children(this._locate("view splitview")).hide();
            },

            _setupLayouts: function (element) {
                var that = this,
                layout;

                element.children(that._locate("layout")).each(function () {
                    if (that.$angular) {
                        layout = compileMobileDirective($(this));
                    } else {
                        layout = kendo.initWidget($(this), {}, ui.roles);
                    }

                    var platform = layout.options.platform;

                    if (!platform || platform === mobile.application.os.name) {
                        that.layouts[layout.options.id] = layout;
                    } else {
                        layout.destroy();
                    }
                });

            }
        });

        kendo.mobile.ViewEngine = ViewEngine;

        ui.plugin(View);
        ui.plugin(Layout);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.mobile.ui,
        Widget = ui.Widget,
        CAPTURE_EVENTS = $.map(kendo.eventMap, function (value) { return value; }).join(" ").split(" ");

        var Loader = Widget.extend({
            init: function (container, options) {
                var that = this,
                element = $('<div class="km-loader"><span class="km-loading km-spin"></span><span class="km-loading-left"></span><span class="km-loading-right"></span></div>');

                Widget.fn.init.call(that, element, options);

                that.container = container;
                that.captureEvents = false;

                that._attachCapture();

                element.append(that.options.loading).hide().appendTo(container);
            },

            options: {
                name: "Loader",
                loading: "<h1>Loading...</h1>",
                timeout: 100
            },

            show: function () {
                var that = this;

                clearTimeout(that._loading);

                if (that.options.loading === false) {
                    return;
                }

                that.captureEvents = true;
                that._loading = setTimeout(function () {
                    that.element.show();
                }, that.options.timeout);
            },

            hide: function () {
                this.captureEvents = false;
                clearTimeout(this._loading);
                this.element.hide();
            },

            changeMessage: function (message) {
                this.options.loading = message;
                this.element.find(">h1").html(message);
            },

            transition: function () {
                this.captureEvents = true;
                this.container.css("pointer-events", "none");
            },

            transitionDone: function () {
                this.captureEvents = false;
                this.container.css("pointer-events", "");
            },

            _attachCapture: function () {
                var that = this;
                that.captureEvents = false;

                function capture(e) {
                    if (that.captureEvents) {
                        e.preventDefault();
                    }
                }

                for (var i = 0; i < CAPTURE_EVENTS.length; i++) {
                    that.container[0].addEventListener(CAPTURE_EVENTS[i], capture, true);
                }
            }
        });

        ui.plugin(Loader);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        mobile = kendo.mobile,
        roleSelector = kendo.roleSelector,
        ui = mobile.ui,
        Widget = ui.Widget,
        ViewEngine = mobile.ViewEngine,
        View = ui.View,
        Loader = mobile.ui.Loader,

        EXTERNAL = "external",
        HREF = "href",
        DUMMY_HREF = "#!",

        NAVIGATE = "navigate",
        VIEW_SHOW = "viewShow",
        SAME_VIEW_REQUESTED = "sameViewRequested",
        OS = kendo.support.mobileOS,
        SKIP_TRANSITION_ON_BACK_BUTTON = OS.ios && !OS.appMode && OS.flatVersion >= 700,

        WIDGET_RELS = /popover|actionsheet|modalview|drawer/,
        BACK = "#:back",

        attrValue = kendo.attrValue,
        // navigation element roles
        buttonRoles = "button backbutton detailbutton listview-link",
        linkRoles = "tab";

        var Pane = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                options = that.options;
                element = that.element;

                element.addClass("km-pane");

                if (that.options.collapsible) {
                    element.addClass("km-collapsible-pane");
                }

                this.history = [];

                this.historyCallback = function (url, params, backButtonPressed) {
                    var transition = that.transition;
                    that.transition = null;

                    // swiping back in iOS leaves the app in a very broken state if we perform a transition
                    if (SKIP_TRANSITION_ON_BACK_BUTTON && backButtonPressed) {
                        transition = "none";
                    }

                    return that.viewEngine.showView(url, transition, params);
                };

                this._historyNavigate = function (url) {
                    if (url === BACK) {
                        if (that.history.length === 1) {
                            return;
                        }

                        that.history.pop();
                        url = that.history[that.history.length - 1];
                    } else {
                        that.history.push(url);
                    }

                    that.historyCallback(url, kendo.parseQueryStringParams(url));
                };

                this._historyReplace = function (url) {
                    var params = kendo.parseQueryStringParams(url);
                    that.history[that.history.length - 1] = url;
                    that.historyCallback(url, params);
                };

                that.loader = new Loader(element, {
                    loading: that.options.loading
                });

                that.viewEngine = new ViewEngine({
                    container: element,
                    transition: options.transition,
                    modelScope: options.modelScope,
                    rootNeeded: !options.initial,
                    serverNavigation: options.serverNavigation,
                    remoteViewURLPrefix: options.root || "",
                    layout: options.layout,
                    $angular: options.$angular,
                    loader: that.loader,

                    showStart: function () {
                        that.loader.transition();
                        that.closeActiveDialogs();
                    },

                    after: function (e) {
                        that.loader.transitionDone();
                    },

                    viewShow: function (e) {
                        that.trigger(VIEW_SHOW, e);
                    },

                    loadStart: function () {
                        that.loader.show();
                    },

                    loadComplete: function () {
                        that.loader.hide();
                    },

                    sameViewRequested: function () {
                        that.trigger(SAME_VIEW_REQUESTED);
                    },

                    viewTypeDetermined: function (e) {
                        if (!e.remote || !that.options.serverNavigation) {
                            that.trigger(NAVIGATE, { url: e.url });
                        }
                    }
                });


                this._setPortraitWidth();

                kendo.onResize(function () {
                    that._setPortraitWidth();
                });

                that._setupAppLinks();
            },

            closeActiveDialogs: function () {
                var dialogs = this.element.find(roleSelector("actionsheet popover modalview")).filter(":visible");
                dialogs.each(function () {
                    kendo.widgetInstance($(this), ui).close();
                });
            },

            navigateToInitial: function () {
                var initial = this.options.initial;

                if (initial) {
                    this.navigate(initial);
                }
            },

            options: {
                name: "Pane",
                portraitWidth: "",
                transition: "",
                layout: "",
                collapsible: false,
                initial: null,
                modelScope: window,
                loading: "<h1>Loading...</h1>"
            },

            events: [
            NAVIGATE,
            VIEW_SHOW,
            SAME_VIEW_REQUESTED
        ],

            append: function (html) {
                return this.viewEngine.append(html);
            },

            destroy: function () {
                Widget.fn.destroy.call(this);
                this.viewEngine.destroy();
                this.userEvents.destroy();
            },

            navigate: function (url, transition) {
                if (url instanceof View) {
                    url = url.id;
                }

                this.transition = transition;

                this._historyNavigate(url);
            },

            replace: function (url, transition) {
                if (url instanceof View) {
                    url = url.id;
                }

                this.transition = transition;

                this._historyReplace(url);
            },

            bindToRouter: function (router) {
                var that = this,
                history = this.history,
                viewEngine = this.viewEngine;

                router.bind("init", function (e) {
                    var url = e.url,
                    attrUrl = router.pushState ? url : "/";

                    viewEngine.rootView.attr(kendo.attr("url"), attrUrl);

                    // if current is set, then this means that the pane has navigated to a given view - we need to update the router accordingly.
                    var length = history.length;

                    if (url === "/" && length) {
                        router.navigate(history[length - 1], true);
                        e.preventDefault(); // prevents from executing routeMissing, by default
                    }
                });

                router.bind("routeMissing", function (e) {
                    if (!that.historyCallback(e.url, e.params, e.backButtonPressed)) {
                        e.preventDefault();
                    }
                });

                router.bind("same", function () {
                    that.trigger(SAME_VIEW_REQUESTED);
                });

                that._historyNavigate = function (url) {
                    router.navigate(url);
                };

                that._historyReplace = function (url) {
                    router.replace(url);
                };
            },

            hideLoading: function () {
                this.loader.hide();
            },

            showLoading: function () {
                this.loader.show();
            },

            changeLoadingMessage: function (message) {
                this.loader.changeMessage(message);
            },

            view: function () {
                return this.viewEngine.view();
            },

            _setPortraitWidth: function () {
                var width,
                portraitWidth = this.options.portraitWidth;

                if (portraitWidth) {
                    width = kendo.mobile.application.element.is(".km-vertical") ? portraitWidth : "auto";
                    this.element.css("width", width);
                }
            },

            _setupAppLinks: function () {
                var that = this;
                this.element.handler(this)
                .on("down", roleSelector(linkRoles), "_mouseup")
                .on("click", roleSelector(linkRoles + " " + buttonRoles), "_appLinkClick");

                this.userEvents = new kendo.UserEvents(this.element, {
                    filter: roleSelector(buttonRoles),
                    tap: function (e) {
                        e.event.currentTarget = e.touch.currentTarget;
                        that._mouseup(e.event);
                    }
                });
            },

            _appLinkClick: function (e) {
                var href = $(e.currentTarget).attr("href");
                var remote = href && href[0] !== "#" && this.options.serverNavigation;

                if (!remote && attrValue($(e.currentTarget), "rel") != EXTERNAL) {
                    e.preventDefault();
                }
            },

            _mouseup: function (e) {
                if (e.which > 1 || e.isDefaultPrevented()) {
                    return;
                }

                var pane = this,
                link = $(e.currentTarget),
                transition = attrValue(link, "transition"),
                rel = attrValue(link, "rel") || "",
                target = attrValue(link, "target"),
                href = link.attr(HREF),
                delayedTouchEnd = SKIP_TRANSITION_ON_BACK_BUTTON && link[0].offsetHeight === 0,
                remote = href && href[0] !== "#" && this.options.serverNavigation;

                if (delayedTouchEnd || remote || rel === EXTERNAL || (typeof href === "undefined") || href === DUMMY_HREF) {
                    return;
                }

                // Prevent iOS address bar progress display for in app navigation
                link.attr(HREF, DUMMY_HREF);
                setTimeout(function () { link.attr(HREF, href); });

                if (rel.match(WIDGET_RELS)) {
                    kendo.widgetInstance($(href), ui).openFor(link);
                    // if propagation is not stopped and actionsheet is opened from tabstrip,
                    // the actionsheet is closed immediately.
                    if (rel === "actionsheet" || rel === "drawer") {
                        e.stopPropagation();
                    }
                } else {
                    if (target === "_top") {
                        pane = mobile.application.pane;
                    }
                    else if (target) {
                        pane = $("#" + target).data("kendoMobilePane");
                    }

                    pane.navigate(href, transition);
                }

                e.preventDefault();
            }
        });

        Pane.wrap = function (element) {
            if (!element.is(roleSelector("view"))) {
                element = element.wrap('<div data-' + kendo.ns + 'role="view" data-stretch="true"></div>').parent();
            }

            var paneContainer = element.wrap('<div class="km-pane-wrapper"><div></div></div>').parent(),
            pane = new Pane(paneContainer);

            pane.navigate("");

            return pane;
        };
        ui.plugin(Pane);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        mobile = kendo.mobile,
        ui = mobile.ui,
        SHOW = "show",
        HIDE = "hide",
        OPEN = "open",
        CLOSE = "close",
        WRAPPER = '<div class="km-popup-wrapper" />',
        ARROW = '<div class="km-popup-arrow" />',
        OVERLAY = '<div class="km-popup-overlay" />',
        DIRECTION_CLASSES = "km-up km-down km-left km-right",
        Widget = ui.Widget,
        DIRECTIONS = {
            "down": {
                origin: "bottom center",
                position: "top center"
            },
            "up": {
                origin: "top center",
                position: "bottom center"
            },
            "left": {
                origin: "center left",
                position: "center right",
                collision: "fit flip"
            },
            "right": {
                origin: "center right",
                position: "center left",
                collision: "fit flip"
            }
        },

        ANIMATION = {
            animation: {
                open: {
                    effects: "fade:in",
                    duration: 0
                },
                close: {
                    effects: "fade:out",
                    duration: 400
                }
            }
        },
        DIMENSIONS = {
            "horizontal": { offset: "top", size: "height" },
            "vertical": { offset: "left", size: "width" }
        },

        REVERSE = {
            "up": "down",
            "down": "up",
            "left": "right",
            "right": "left"
        };

        var Popup = Widget.extend({
            init: function (element, options) {
                var that = this,
                containerPopup = element.closest(".km-modalview-wrapper"),
                viewport = element.closest(".km-root").children('.km-pane').first(),
                container = containerPopup[0] ? containerPopup : viewport,
                popupOptions,
                axis;

                if (options.viewport) {
                    viewport = options.viewport;
                } else if (!viewport[0]) {
                    viewport = window;
                }

                if (options.container) {
                    container = options.container;
                } else if (!container[0]) {
                    container = document.body;
                }

                popupOptions = {
                    viewport: viewport,
                    copyAnchorStyles: false,
                    autosize: true,
                    open: function () {
                        that.overlay.show();
                    },

                    activate: $.proxy(that._activate, that),

                    deactivate: function () {
                        that.overlay.hide();
                        if (!that._apiCall) {
                            that.trigger(HIDE);
                        }

                        that._apiCall = false;
                    }
                };

                Widget.fn.init.call(that, element, options);

                element = that.element;
                options = that.options;

                element.wrap(WRAPPER).addClass("km-popup").show();

                axis = that.options.direction.match(/left|right/) ? "horizontal" : "vertical";

                that.dimensions = DIMENSIONS[axis];

                that.wrapper = element.parent().css({
                    width: options.width,
                    height: options.height
                }).addClass("km-popup-wrapper km-" + options.direction).hide();

                that.arrow = $(ARROW).prependTo(that.wrapper).hide();

                that.overlay = $(OVERLAY).appendTo(container).hide();
                popupOptions.appendTo = that.overlay;

                if (options.className) {
                    that.overlay.addClass(options.className);
                }

                that.popup = new kendo.ui.Popup(that.wrapper, $.extend(true, popupOptions, ANIMATION, DIRECTIONS[options.direction]));
            },

            options: {
                name: "Popup",
                width: 240,
                height: "",
                direction: "down",
                container: null,
                viewport: null
            },

            events: [
            HIDE
        ],

            show: function (target) {
                this.popup.options.anchor = $(target);
                this.popup.open();
            },

            hide: function () {
                this._apiCall = true;
                this.popup.close();
            },

            destroy: function () {
                Widget.fn.destroy.call(this);
                this.popup.destroy();
                this.overlay.remove();
            },

            target: function () {
                return this.popup.options.anchor;
            },

            _activate: function () {
                var that = this,
                direction = that.options.direction,
                dimensions = that.dimensions,
                offset = dimensions.offset,
                popup = that.popup,
                anchor = popup.options.anchor,
                anchorOffset = $(anchor).offset(),
                elementOffset = $(popup.element).offset(),
                cssClass = popup.flipped ? REVERSE[direction] : direction,
                min = that.arrow[dimensions.size]() * 2,
                max = that.element[dimensions.size]() - that.arrow[dimensions.size](),
                size = $(anchor)[dimensions.size](),
                offsetAmount = anchorOffset[offset] - elementOffset[offset] + (size / 2);

                if (offsetAmount < min) {
                    offsetAmount = min;
                }

                if (offsetAmount > max) {
                    offsetAmount = max;
                }

                that.wrapper.removeClass(DIRECTION_CLASSES).addClass("km-" + cssClass);
                that.arrow.css(offset, offsetAmount).show();
            }
        });

        var PopOver = Widget.extend({
            init: function (element, options) {
                var that = this,
                popupOptions,
                paneOptions;

                that.initialOpen = false;

                Widget.fn.init.call(that, element, options);

                popupOptions = $.extend({
                    className: "km-popover-root",
                    hide: function () { that.trigger(CLOSE); }
                }, this.options.popup);

                that.popup = new Popup(that.element, popupOptions);
                that.popup.overlay.on("move", function (e) {
                    if (e.target == that.popup.overlay[0]) {
                        e.preventDefault();
                    }
                });

                that.pane = new ui.Pane(that.element, $.extend(this.options.pane, { $angular: this.options.$angular }));
                that.pane.navigateToInitial();

                kendo.notify(that, ui);
            },

            options: {
                name: "PopOver",
                popup: {},
                pane: {}
            },

            events: [
            OPEN,
            CLOSE
        ],

            open: function (target) {
                this.popup.show(target);

                if (!this.initialOpen) {
                    this.pane.navigate("");
                    this.popup.popup._position();
                    this.initialOpen = true;
                } else {
                    this.pane.view()._invokeNgController();
                }
            },

            openFor: function (target) {
                this.open(target);
                this.trigger(OPEN, { target: this.popup.target() });
            },

            close: function () {
                this.popup.hide();
            },

            destroy: function () {
                Widget.fn.destroy.call(this);
                this.pane.destroy();
                this.popup.destroy();

                kendo.destroy(this.element);
            }
        });

        ui.plugin(Popup);
        ui.plugin(PopOver);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.mobile.ui,
        Popup = kendo.ui.Popup,
        SHIM = '<div class="km-shim"/>',
        SHOW = "show",
        HIDE = "hide",
        Widget = ui.Widget;

        var Shim = Widget.extend({
            init: function (element, options) {
                var that = this,
                app = kendo.mobile.application,
                os = kendo.support.mobileOS,
                osname = app ? app.os.name : (os ? os.name : "ios"),
                ioswp = osname === "ios" || osname === "wp" || (app ? app.os.skin : false),
                bb = osname === "blackberry",
                align = options.align || (ioswp ? "bottom center" : bb ? "center right" : "center center"),
                position = options.position || (ioswp ? "bottom center" : bb ? "center right" : "center center"),
                effect = options.effect || (ioswp ? "slideIn:up" : bb ? "slideIn:left" : "fade:in"),
                shim = $(SHIM).handler(that).hide();

                Widget.fn.init.call(that, element, options);

                that.shim = shim;
                element = that.element;
                options = that.options;

                if (options.className) {
                    that.shim.addClass(options.className);
                }

                if (!options.modal) {
                    that.shim.on("up", "_hide");
                }

                (app ? app.element : $(document.body)).append(shim);

                that.popup = new Popup(that.element, {
                    anchor: shim,
                    modal: true,
                    appendTo: shim,
                    origin: align,
                    position: position,
                    animation: {
                        open: {
                            effects: effect,
                            duration: options.duration
                        },
                        close: {
                            duration: options.duration
                        }
                    },

                    close: function (e) {
                        var prevented = false;

                        if (!that._apiCall) {
                            prevented = that.trigger(HIDE);
                        }

                        if (prevented) {
                            e.preventDefault();
                        }

                        that._apiCall = false;
                    },

                    deactivate: function () { // Deactivate event can't be prevented.
                        shim.hide();
                    },

                    open: function () {
                        shim.show();
                    }
                });

                kendo.notify(that);
            },

            events: [HIDE],

            options: {
                name: "Shim",
                modal: false,
                align: undefined,
                position: undefined,
                effect: undefined,
                duration: 200
            },

            show: function () {
                this.popup.open();
            },

            hide: function () {
                this._apiCall = true;
                this.popup.close();
            },

            destroy: function () {
                Widget.fn.destroy.call(this);
                this.shim.kendoDestroy();
                this.popup.destroy();
                this.shim.remove();
            },

            _hide: function (e) {
                if (!e || !$.contains(this.shim.children().children(".k-popup")[0], e.target)) {
                    this.popup.close();
                }
            }
        });

        ui.plugin(Shim);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        support = kendo.support,
        ui = kendo.mobile.ui,
        Shim = ui.Shim,
        Popup = ui.Popup,
        Widget = ui.Widget,
        OPEN = "open",
        CLOSE = "close",
        COMMAND = "command",
        BUTTONS = "li>a",
        CONTEXT_DATA = "actionsheetContext",
        WRAP = '<div class="km-actionsheet-wrapper" />',
        cancelTemplate = kendo.template('<li class="km-actionsheet-cancel"><a href="\\#">#:cancel#</a></li>');

        var ActionSheet = Widget.extend({
            init: function (element, options) {
                var that = this,
                ShimClass,
                tablet,
                type,
                os = support.mobileOS;

                Widget.fn.init.call(that, element, options);

                options = that.options;
                type = options.type;
                element = that.element;

                if (type === "auto") {
                    tablet = os && os.tablet;
                } else {
                    tablet = type === "tablet";
                }

                ShimClass = tablet ? Popup : Shim;

                if (options.cancelTemplate) {
                    cancelTemplate = kendo.template(options.cancelTemplate);
                }

                element
                .addClass("km-actionsheet")
                .append(cancelTemplate({ cancel: that.options.cancel }))
                .wrap(WRAP)
                .on("up", BUTTONS, "_click")
                .on("click", BUTTONS, kendo.preventDefault);

                that.view().bind("destroy", function () {
                    that.destroy();
                });

                that.wrapper = element.parent().addClass(type ? " km-actionsheet-" + type : "");

                that.shim = new ShimClass(that.wrapper, $.extend({ modal: os.ios && os.majorVersion < 7, className: "km-actionsheet-root" }, that.options.popup));

                that._closeProxy = $.proxy(that, "_close");
                that.shim.bind("hide", that._closeProxy);

                if (tablet) {
                    kendo.onResize(that._closeProxy);
                }

                kendo.notify(that, ui);
            },

            events: [
            OPEN,
            CLOSE,
            COMMAND
        ],

            options: {
                name: "ActionSheet",
                cancel: "Cancel",
                type: "auto",
                popup: { height: "auto" }
            },

            open: function (target, context) {
                var that = this;
                that.target = $(target);
                that.context = context;
                that.shim.show(target);
            },

            close: function () {
                this.context = this.target = null;
                this.shim.hide();
            },

            openFor: function (target) {
                var that = this,
                context = target.data(CONTEXT_DATA);

                that.open(target, context);
                that.trigger(OPEN, { target: target, context: context });
            },

            destroy: function () {
                Widget.fn.destroy.call(this);
                kendo.unbindResize(this._closeProxy);
                this.shim.destroy();
            },

            _click: function (e) {
                if (e.isDefaultPrevented()) {
                    return;
                }

                var currentTarget = $(e.currentTarget);
                var action = currentTarget.data("action");

                if (action) {
                    var actionData = {
                        target: this.target,
                        context: this.context
                    },
                $angular = this.options.$angular;

                    if ($angular) {
                        this.element.injector().get("$parse")(action)($angular[0])(actionData);
                    } else {
                        kendo.getter(action)(window)(actionData);
                    }
                }

                this.trigger(COMMAND, { target: this.target, context: this.context, currentTarget: currentTarget });

                e.preventDefault();
                this._close();
            },

            _close: function (e) {
                if (!this.trigger(CLOSE)) {
                    this.close();
                } else {
                    e.preventDefault();
                }
            }
        });

        ui.plugin(ActionSheet);
    })(window.kendo.jQuery);





    /* jshint eqnull: true */
    (function ($, undefined) {
        var kendo = window.kendo,
        ui = kendo.ui,
        DataSource = kendo.data.DataSource,
        Groupable = ui.Groupable,
        tbodySupportsInnerHtml = kendo.support.tbodyInnerHtml,
        activeElement = kendo._activeElement,
        Widget = ui.Widget,
        keys = kendo.keys,
        isPlainObject = $.isPlainObject,
        extend = $.extend,
        map = $.map,
        grep = $.grep,
        isArray = $.isArray,
        inArray = $.inArray,
        push = Array.prototype.push,
        proxy = $.proxy,
        isFunction = kendo.isFunction,
        isEmptyObject = $.isEmptyObject,
        math = Math,
        PROGRESS = "progress",
        ERROR = "error",
        DATA_CELL = ":not(.k-group-cell):not(.k-hierarchy-cell):visible",
        SELECTION_CELL_SELECTOR = "tbody>tr:not(.k-grouping-row):not(.k-detail-row):not(.k-group-footer) > td:not(.k-group-cell):not(.k-hierarchy-cell)",
        NAVROW = "tr:not(.k-footer-template):visible",
        NAVCELL = ":not(.k-group-cell):not(.k-hierarchy-cell):visible",
        FIRSTNAVITEM = NAVROW + ":first>" + NAVCELL + ":first",
        HEADERCELLS = "th.k-header:not(.k-group-cell):not(.k-hierarchy-cell)",
        NS = ".kendoGrid",
        EDIT = "edit",
        SAVE = "save",
        REMOVE = "remove",
        DETAILINIT = "detailInit",
        FILTERMENUINIT = "filterMenuInit",
        COLUMNMENUINIT = "columnMenuInit",
        CHANGE = "change",
        COLUMNHIDE = "columnHide",
        COLUMNSHOW = "columnShow",
        SAVECHANGES = "saveChanges",
        DATABOUND = "dataBound",
        DETAILEXPAND = "detailExpand",
        DETAILCOLLAPSE = "detailCollapse",
        FOCUSED = "k-state-focused",
        SELECTED = "k-state-selected",
        COLUMNRESIZE = "columnResize",
        COLUMNREORDER = "columnReorder",
        COLUMNLOCK = "columnLock",
        COLUMNUNLOCK = "columnUnlock",
        CLICK = "click",
        HEIGHT = "height",
        TABINDEX = "tabIndex",
        FUNCTION = "function",
        STRING = "string",
        DELETECONFIRM = "Are you sure you want to delete this record?",
        CONFIRMDELETE = "Delete",
        CANCELDELETE = "Cancel",
        formatRegExp = /(\}|\#)/ig,
        templateHashRegExp = /#/ig,
        whitespaceRegExp = "[\\x20\\t\\r\\n\\f]",
        nonDataCellsRegExp = new RegExp("(^|" + whitespaceRegExp + ")" + "(k-group-cell|k-hierarchy-cell)" + "(" + whitespaceRegExp + "|$)"),
        filterRowRegExp = new RegExp("(^|" + whitespaceRegExp + ")" + "(k-filter-row)" + "(" + whitespaceRegExp + "|$)"),
        COMMANDBUTTONTMPL = '<a class="k-button k-button-icontext #=className#" #=attr# href="\\#"><span class="#=iconClass# #=imageClass#"></span>#=text#</a>',
        isRtl = false,
        browser = kendo.support.browser,
        isIE7 = browser.msie && browser.version == 7,
        isIE8 = browser.msie && browser.version == 8;

        var VirtualScrollable = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);
                that._refreshHandler = proxy(that.refresh, that);
                that.setDataSource(options.dataSource);
                that.wrap();
            },

            setDataSource: function (dataSource) {
                var that = this;
                if (that.dataSource) {
                    that.dataSource.unbind(CHANGE, that._refreshHandler);
                }
                that.dataSource = dataSource;
                that.dataSource.bind(CHANGE, that._refreshHandler);
            },

            options: {
                name: "VirtualScrollable",
                itemHeight: $.noop
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.dataSource.unbind(CHANGE, that._refreshHandler);
                that.wrapper.add(that.verticalScrollbar).off(NS);

                if (that.drag) {
                    that.drag.destroy();
                    that.drag = null;
                }
                that.wrapper = that.element = that.verticalScrollbar = null;
                that._refreshHandler = null;
            },

            wrap: function () {
                var that = this,
                // workaround for IE issue where scroll is not raised if container is same width as the scrollbar
                scrollbar = kendo.support.scrollbar() + 1,
                element = that.element,
                wrapper;

                element.css({
                    width: "auto",
                    overflow: "hidden"
                }).css((isRtl ? "padding-left" : "padding-right"), scrollbar);
                that.content = element.children().first();
                wrapper = that.wrapper = that.content.wrap('<div class="k-virtual-scrollable-wrap"/>')
                                .parent()
                                .bind("DOMMouseScroll" + NS + " mousewheel" + NS, proxy(that._wheelScroll, that));

                if (kendo.support.kineticScrollNeeded) {
                    that.drag = new kendo.UserEvents(that.wrapper, {
                        global: true,
                        start: function (e) {
                            e.sender.capture();
                        },
                        move: function (e) {
                            that.verticalScrollbar.scrollTop(that.verticalScrollbar.scrollTop() - e.y.delta);
                            wrapper.scrollLeft(wrapper.scrollLeft() - e.x.delta);
                            e.preventDefault();
                        }
                    });
                }

                that.verticalScrollbar = $('<div class="k-scrollbar k-scrollbar-vertical" />')
                                        .css({
                                            width: scrollbar
                                        }).appendTo(element)
                                        .bind("scroll" + NS, proxy(that._scroll, that));
            },

            _wheelScroll: function (e) {
                if (e.ctrlKey) {
                    return;
                }

                var scrollTop = this.verticalScrollbar.scrollTop(),
                delta = kendo.wheelDeltaY(e);

                if (delta) {
                    e.preventDefault();
                    //In Firefox DOMMouseScroll event cannot be canceled
                    $(e.currentTarget).one("wheel" + NS, false);
                    this.verticalScrollbar.scrollTop(scrollTop + (-delta));
                }
            },

            _scroll: function (e) {
                var that = this,
                scrollTop = e.currentTarget.scrollTop,
                dataSource = that.dataSource,
                rowHeight = that.itemHeight,
                skip = dataSource.skip() || 0,
                start = that._rangeStart || skip,
                height = that.element.innerHeight(),
                isScrollingUp = !!(that._scrollbarTop && that._scrollbarTop > scrollTop),
                firstItemIndex = math.max(math.floor(scrollTop / rowHeight), 0),
                lastItemIndex = math.max(firstItemIndex + math.floor(height / rowHeight), 0);

                that._scrollTop = scrollTop - (start * rowHeight);
                that._scrollbarTop = scrollTop;

                if (!that._fetch(firstItemIndex, lastItemIndex, isScrollingUp)) {
                    that.wrapper[0].scrollTop = that._scrollTop;
                }
            },

            _fetch: function (firstItemIndex, lastItemIndex, scrollingUp) {
                var that = this,
                dataSource = that.dataSource,
                itemHeight = that.itemHeight,
                take = dataSource.take(),
                rangeStart = that._rangeStart || dataSource.skip() || 0,
                currentSkip = math.floor(firstItemIndex / take) * take,
                fetching = false,
                prefetchAt = 0.33;

                if (firstItemIndex < rangeStart) {

                    fetching = true;
                    rangeStart = math.max(0, lastItemIndex - take);
                    that._scrollTop = (firstItemIndex - rangeStart) * itemHeight;
                    that._page(rangeStart, take);

                } else if (lastItemIndex >= rangeStart + take && !scrollingUp) {

                    fetching = true;
                    rangeStart = firstItemIndex;
                    that._scrollTop = itemHeight;
                    that._page(rangeStart, take);

                } else if (!that._fetching) {

                    if (firstItemIndex < (currentSkip + take) - take * prefetchAt && firstItemIndex > take) {
                        dataSource.prefetch(currentSkip - take, take);
                    }
                    if (lastItemIndex > currentSkip + take * prefetchAt) {
                        dataSource.prefetch(currentSkip + take, take);
                    }

                }
                return fetching;
            },

            _page: function (skip, take) {
                var that = this,
                dataSource = that.dataSource;

                clearTimeout(that._timeout);
                that._fetching = true;
                that._rangeStart = skip;

                if (dataSource.inRange(skip, take)) {
                    dataSource.range(skip, take);
                } else {
                    kendo.ui.progress(that.wrapper.parent(), true);
                    that._timeout = setTimeout(function () {
                        dataSource.range(skip, take);
                    }, 100);
                }
            },

            refresh: function () {
                var that = this,
                html = "",
                maxHeight = 250000,
                dataSource = that.dataSource,
                rangeStart = that._rangeStart,
                scrollbar = !kendo.support.kineticScrollNeeded ? kendo.support.scrollbar() : 0,
                wrapperElement = that.wrapper[0],
                totalHeight,
                idx,
                itemHeight;

                kendo.ui.progress(that.wrapper.parent(), false);
                clearTimeout(that._timeout);

                itemHeight = that.itemHeight = that.options.itemHeight() || 0;

                var addScrollBarHeight = (wrapperElement.scrollWidth > wrapperElement.offsetWidth) ? scrollbar : 0;

                totalHeight = dataSource.total() * itemHeight + addScrollBarHeight;

                for (idx = 0; idx < math.floor(totalHeight / maxHeight); idx++) {
                    html += '<div style="width:1px;height:' + maxHeight + 'px"></div>';
                }

                if (totalHeight % maxHeight) {
                    html += '<div style="width:1px;height:' + (totalHeight % maxHeight) + 'px"></div>';
                }

                that.verticalScrollbar.html(html);
                wrapperElement.scrollTop = that._scrollTop;

                if (that.drag) {
                    that.drag.cancel();
                }

                if (rangeStart && !that._fetching) { // we are rebound from outside local range should be reset
                    that._rangeStart = dataSource.skip();

                    if (dataSource.page() === 1) {// reset the scrollbar position if datasource is filtered
                        that.verticalScrollbar[0].scrollTop = 0;
                    }
                }
                that._fetching = false;
            }
        });

        function groupCells(count) {
            return new Array(count + 1).join('<td class="k-group-cell">&nbsp;</td>');
        }

        function stringifyAttributes(attributes) {
            var attr,
            result = " ";

            if (attributes) {
                if (typeof attributes === STRING) {
                    return attributes;
                }

                for (attr in attributes) {
                    result += attr + '="' + attributes[attr] + '"';
                }
            }
            return result;
        }

        var defaultCommands = {
            create: {
                text: "Add new record",
                imageClass: "k-add",
                className: "k-grid-add",
                iconClass: "k-icon"
            },
            cancel: {
                text: "Cancel changes",
                imageClass: "k-cancel",
                className: "k-grid-cancel-changes",
                iconClass: "k-icon"
            },
            save: {
                text: "Save changes",
                imageClass: "k-update",
                className: "k-grid-save-changes",
                iconClass: "k-icon"
            },
            destroy: {
                text: "Delete",
                imageClass: "k-delete",
                className: "k-grid-delete",
                iconClass: "k-icon"
            },
            edit: {
                text: "Edit",
                imageClass: "k-edit",
                className: "k-grid-edit",
                iconClass: "k-icon"
            },
            update: {
                text: "Update",
                imageClass: "k-update",
                className: "k-primary k-grid-update",
                iconClass: "k-icon"
            },
            canceledit: {
                text: "Cancel",
                imageClass: "k-cancel",
                className: "k-grid-cancel",
                iconClass: "k-icon"
            },
            excel: {
                text: "Export to Excel",
                imageClass: "k-i-excel",
                className: "k-grid-excel",
                iconClass: "k-icon"
            },
            pdf: {
                text: "Export to PDF",
                imageClass: "k-i-pdf",
                className: "k-grid-pdf",
                iconClass: "k-icon"
            }
        };

        function heightAboveHeader(context) {
            var top = 0;
            $('> .k-grouping-header, > .k-grid-toolbar', context).each(function () {
                top += this.offsetHeight;
            });
            return top;
        }

        function cursor(context, value) {
            $('th, th .k-grid-filter, th .k-link', context)
            .add(document.body)
            .css('cursor', value);
        }

        function buildEmptyAggregatesObject(aggregates) {
            var idx,
                length,
                aggregate = {},
                fieldsMap = {};

            if (!isEmptyObject(aggregates)) {
                if (!isArray(aggregates)) {
                    aggregates = [aggregates];
                }

                for (idx = 0, length = aggregates.length; idx < length; idx++) {
                    aggregate[aggregates[idx].aggregate] = 0;
                    fieldsMap[aggregates[idx].field] = aggregate;
                }
            }

            return fieldsMap;
        }

        function reorder(selector, source, dest, before, count) {
            var sourceIndex = source;
            source = $();
            count = count || 1;
            for (var idx = 0; idx < count; idx++) {
                source = source.add(selector.eq(sourceIndex + idx));
            }

            if (typeof dest == "number") {
                source[before ? "insertBefore" : "insertAfter"](selector.eq(dest));
            } else {
                source.appendTo(dest);
            }
        }

        function elements(lockedContent, content, filter) {
            return $(lockedContent).add(content).find(filter);
        }

        function attachCustomCommandEvent(context, container, commands) {
            var idx,
            length,
            command,
            commandName;

            commands = !isArray(commands) ? [commands] : commands;

            for (idx = 0, length = commands.length; idx < length; idx++) {
                command = commands[idx];

                if (isPlainObject(command) && command.click) {
                    commandName = command.name || command.text;
                    container.on(CLICK + NS, "a.k-grid-" + (commandName || "").replace(/\s/g, ""), { commandName: commandName }, proxy(command.click, context));
                }
            }
        }

        function normalizeColumns(columns, encoded, hide) {
            return map(columns, function (column) {
                column = typeof column === STRING ? { field: column} : column;

                var hidden;

                if (!isVisible(column) || hide) {
                    column.attributes = addHiddenStyle(column.attributes);
                    column.footerAttributes = addHiddenStyle(column.footerAttributes);
                    column.headerAttributes = addHiddenStyle(column.headerAttributes);
                    hidden = true;
                }

                if (column.columns) {
                    column.columns = normalizeColumns(column.columns, encoded, hidden);
                }

                return extend({ encoded: encoded, hidden: hidden }, column);
            });
        }

        function columnParent(column, columns) {
            var parents = [];
            columnParents(column, columns, parents);
            return parents[parents.length - 1];
        }

        function columnParents(column, columns, parents) {
            parents = parents || [];

            for (var idx = 0; idx < columns.length; idx++) {
                if (column === columns[idx]) {
                    return true;
                } else if (columns[idx].columns) {
                    var inserted = parents.length;
                    parents.push(columns[idx]);
                    if (!columnParents(column, columns[idx].columns, parents)) {
                        parents.splice(inserted, parents.length - inserted);
                    } else {
                        return true;
                    }
                }
            }
            return false;
        }

        function setColumnVisibility(column, visible) {
            var method = visible ? removeHiddenStyle : addHiddenStyle;
            column.hidden = !visible;
            column.attributes = method(column.attributes);
            column.footerAttributes = method(column.footerAttributes);
            column.headerAttributes = method(column.headerAttributes);
        }

        function isVisible(column) {
            return visibleColumns([column]).length > 0;
        }

        function visibleColumns(columns) {
            return grep(columns, function (column) {
                var result = !column.hidden;
                if (result && column.columns) {
                    result = visibleColumns(column.columns).length > 0;
                }
                return result;
            });
        }

        function toJQuery(elements) {
            return $(elements).map(function () { return this.toArray(); });
        }

        function updateCellRowSpan(cell, columns, sourceLockedColumnsCount) {
            var lockedColumnDepth = depth(lockedColumns(columns));
            var nonLockedColumnDepth = depth(nonLockedColumns(columns));

            var rowSpan = cell.rowSpan;
            if (sourceLockedColumnsCount) {
                if (lockedColumnDepth > nonLockedColumnDepth) {
                    cell.rowSpan = (rowSpan - (lockedColumnDepth - nonLockedColumnDepth)) || 1;
                } else {
                    cell.rowSpan = rowSpan + (nonLockedColumnDepth - lockedColumnDepth);
                }
            } else {
                if (lockedColumnDepth > nonLockedColumnDepth) {
                    cell.rowSpan = rowSpan + (lockedColumnDepth - nonLockedColumnDepth);
                } else {
                    cell.rowSpan = (rowSpan - (nonLockedColumnDepth - lockedColumnDepth)) || 1;
                }
            }
        }

        function moveCellsBetweenContainers(sources, target, leafs, columns, container, destination, groups) {
            var sourcesDepth = depth(sources);
            var targetDepth = depth([target]);

            if (sourcesDepth > targetDepth) {
                var groupCells = new Array(groups + 1).join('<th class="k-group-cell k-header">&nbsp;</th>');
                var rows = destination.children(":not(.k-filter-row)");
                $(new Array((sourcesDepth - targetDepth) + 1).join("<tr>" + groupCells + "</tr>")).insertAfter(rows.last());
            }

            addRowSpanValue(destination, sourcesDepth - targetDepth);

            moveCells(leafs, columns, container, destination);
        }

        function updateCellIndex(thead, columns, offset) {
            offset = offset || 0;

            var position;
            var cell;
            var allColumns = columns;
            columns = leafColumns(columns);

            for (var idx = 0, length = columns.length; idx < length; idx++) {
                position = columnPosition(columns[idx], allColumns);
                cell = thead.find(">tr:not(.k-filter-row)").eq(position.row).find(".k-header:not(.k-group-cell,.k-hierarchy-cell)").eq(position.cell);
                cell.attr(kendo.attr("index"), offset + idx);
            }

            return columns.length;
        }

        function depth(columns) {
            var result = 1;
            var max = 0;

            for (var idx = 0; idx < columns.length; idx++) {
                if (columns[idx].columns) {
                    var temp = depth(columns[idx].columns);
                    if (temp > max) {
                        max = temp;
                    }
                }
            }
            return result + max;
        }

        function moveCells(leafs, columns, container, destination) {
            var sourcePosition = columnVisiblePosition(leafs[0], columns);

            var ths = container.find(">tr:not(.k-filter-row):eq(" + sourcePosition.row + ")>th.k-header");

            var t = $();
            var sourceIndex = sourcePosition.cell;
            var idx;

            for (idx = 0; idx < leafs.length; idx++) {
                t = t.add(ths.eq(sourceIndex + idx));
            }

            destination.find(">tr:not(.k-filter-row)").eq(sourcePosition.row).append(t);

            var children = [];
            for (idx = 0; idx < leafs.length; idx++) {
                if (leafs[idx].columns) {
                    children = children.concat(leafs[idx].columns);
                }
            }

            if (children.length) {
                moveCells(children, columns, container, destination);
            }
        }

        function columnPosition(column, columns, row, cellCounts) {
            var result;
            var idx;

            row = row || 0;
            cellCounts = cellCounts || {};
            cellCounts[row] = cellCounts[row] || 0;

            for (idx = 0; idx < columns.length; idx++) {
                if (columns[idx] == column) {
                    result = { cell: cellCounts[row], row: row };
                    break;
                } else if (columns[idx].columns) {
                    result = columnPosition(column, columns[idx].columns, row + 1, cellCounts);
                    if (result) {
                        break;
                    }
                }

                cellCounts[row]++;
            }
            return result;
        }

        function findReorderTarget(columns, target, source, before) {
            if (target.columns) {
                target = target.columns;
                return target[before ? 0 : target.length - 1];
            } else {
                var parent = columnParent(target, columns);
                var parentColumns;

                if (parent) {
                    parentColumns = parent.columns;
                } else {
                    parentColumns = columns;
                }

                var index = inArray(target, parentColumns);
                if (index === 0 && before) {
                    index++;
                } else if (index == parentColumns.length - 1 && !before) {
                    index--;
                } else if (index > 0 || (index === 0 && !before)) {
                    index += before ? -1 : 1;
                }

                target = parentColumns[Math.max(index, 0)];
                if (target && target != source && target.columns) {
                    return findReorderTarget(columns, target, source, before);
                }
            }
            return null;
        }


        function columnVisiblePosition(column, columns, row, cellCounts) {
            var result;
            var idx;

            row = row || 0;
            cellCounts = cellCounts || {};
            cellCounts[row] = cellCounts[row] || 0;

            for (idx = 0; idx < columns.length; idx++) {
                if (columns[idx] == column) {
                    result = { cell: cellCounts[row], row: row };
                    break;
                } else if (columns[idx].columns) {
                    result = columnVisiblePosition(column, columns[idx].columns, row + 1, cellCounts);
                    if (result) {
                        break;
                    }
                }

                if (!columns[idx].hidden) {
                    cellCounts[row]++;
                }
            }
            return result;
        }

        function flatColumnsInDomOrder(columns) {
            var result = flatColumns(lockedColumns(columns));
            return result.concat(flatColumns(nonLockedColumns(columns)));
        }

        function flatColumns(columns) {
            var result = [];
            var children = [];
            for (var idx = 0; idx < columns.length; idx++) {
                result.push(columns[idx]);
                if (columns[idx].columns) {
                    children = children.concat(columns[idx].columns);
                }

            }
            if (children.length) {
                result = result.concat(flatColumns(children));
            }
            return result;
        }

        function hiddenLeafColumnsCount(columns) {
            var counter = 0;
            var column;

            for (var idx = 0; idx < columns.length; idx++) {
                column = columns[idx];

                if (column.columns) {
                    counter += hiddenLeafColumnsCount(column.columns);
                } else if (column.hidden) {
                    counter++;
                }
            }
            return counter;
        }

        function columnsWidth(cols) {
            var colWidth, width = 0;

            for (var idx = 0, length = cols.length; idx < length; idx++) {
                colWidth = cols[idx].style.width;
                if (colWidth && colWidth.indexOf("%") == -1) {
                    width += parseInt(colWidth, 10);
                }
            }

            return width;
        }

        function removeRowSpanValue(container, count) {
            var cells = container.find("tr:not(.k-filter-row) th:not(.k-group-cell,.k-hierarchy-cell)");

            var rowSpan;
            for (var idx = 0; idx < cells.length; idx++) {
                rowSpan = cells[idx].rowSpan;
                if (rowSpan > 1) {
                    cells[idx].rowSpan = (rowSpan - count) || 1;
                }
            }
        }

        function addRowSpanValue(container, count) {
            var cells = container.find("tr:not(.k-filter-row) th:not(.k-group-cell,.k-hierarchy-cell)");

            for (var idx = 0; idx < cells.length; idx++) {
                cells[idx].rowSpan += count;
            }
        }

        function removeEmptyRows(container) {
            var rows = container.find("tr:not(.k-filter-row)");

            var emptyRowsCount = rows.filter(function () {
                return !$(this).children().length;
            }).remove().length;

            var cells = rows.find("th:not(.k-group-cell,.k-hierarchy-cell)");

            for (var idx = 0; idx < cells.length; idx++) {
                if (cells[idx].rowSpan > 1) {
                    cells[idx].rowSpan -= emptyRowsCount;
                }
            }
            return rows.length - emptyRowsCount;
        }

        function mapColumnToCellRows(columns, cells, rows, rowIndex, offset) {
            var idx, row, length, children = [];

            for (idx = 0, length = columns.length; idx < length; idx++) {
                row = rows[rowIndex] || [];
                row.push(cells.eq(offset + idx));
                rows[rowIndex] = row;

                if (columns[idx].columns) {
                    children = children.concat(columns[idx].columns);
                }
            }

            if (children.length) {
                mapColumnToCellRows(children, cells, rows, rowIndex + 1, offset + columns.length);
            }
        }

        function lockedColumns(columns) {
            return grep(columns, function (column) {
                return column.locked;
            });
        }

        function nonLockedColumns(columns) {
            return grep(columns, function (column) {
                return !column.locked;
            });
        }

        function visibleNonLockedColumns(columns) {
            return grep(columns, function (column) {
                return !column.locked && isVisible(column);
            });
        }

        function visibleLockedColumns(columns) {
            return grep(columns, function (column) {
                return column.locked && isVisible(column);
            });
        }

        function visibleLeafColumns(columns) {
            var result = [];

            for (var idx = 0; idx < columns.length; idx++) {
                if (columns[idx].hidden) {
                    continue;
                }

                if (columns[idx].columns) {
                    result = result.concat(visibleLeafColumns(columns[idx].columns));
                } else {
                    result.push(columns[idx]);
                }
            }

            return result;
        }

        function leafColumns(columns) {
            var result = [];

            for (var idx = 0; idx < columns.length; idx++) {
                if (!columns[idx].columns) {
                    result.push(columns[idx]);
                    continue;
                }
                result = result.concat(leafColumns(columns[idx].columns));
            }

            return result;
        }

        function leafDataCells(container) {
            var rows = container.find(">tr:not(.k-filter-row)");

            var cells = $();
            if (rows.length > 1) {
                cells = rows.find("th:not(.k-group-cell,.k-hierarchy-cell)")
                .filter(function () { return this.rowSpan > 1; });
            }

            cells = cells.add(rows.last().find("th:not(.k-group-cell,.k-hierarchy-cell)"));

            var indexAttr = kendo.attr("index");
            cells.sort(function (a, b) {
                a = parseInt($(a).attr(indexAttr), 10);
                b = parseInt($(b).attr(indexAttr), 10);
                return a > b ? 1 : (a < b ? -1 : 0);
            });
            return cells;
        }

        function parentColumnsCells(cell) {
            var container = cell.closest("table");
            var result = $().add(cell);

            var row = cell.closest("tr");
            var headerRows = container.find("tr:not(.k-filter-row)");
            var level = headerRows.index(row);
            if (level > 0) {
                var parent = headerRows.eq(level - 1);
                var parentCellsWithChildren = parent.find("th:not(.k-group-cell,.k-hierarchy-cell)").filter(function () {
                    return !$(this).attr("rowspan");
                });

                var offset = 0;
                var index = row.find("th:not(.k-group-cell,.k-hierarchy-cell)").index(cell);

                var prevCells = cell.prevAll(":not(.k-group-cell,.k-hierarchy-cell)").filter(function () {
                    return this.colSpan > 1;
                });

                for (idx = 0; idx < prevCells.length; idx++) {
                    offset += prevCells[idx].colSpan || 1;
                }

                index += Math.max(offset - 1, 0);

                offset = 0;
                for (var idx = 0; idx < parentCellsWithChildren.length; idx++) {
                    var parentCell = parentCellsWithChildren.eq(idx);
                    if (parentCell.attr("colSpan")) {
                        offset += parentCell[0].colSpan;
                    } else {
                        offset += 1;
                    }
                    if (index >= idx && index < offset) {
                        result = parentColumnsCells(parentCell).add(result);
                        break;
                    }
                }
            }
            return result;
        }

        function childColumnsCells(cell) {
            var container = cell.closest("table");
            var result = $().add(cell);

            var row = cell.closest("tr");
            var headerRows = container.find("tr:not(.k-filter-row)");
            var level = headerRows.index(row) + cell[0].rowSpan;
            var colSpanAttr = kendo.attr("colspan");

            if (level <= headerRows.length - 1) {
                var child = row.next();
                var index = row.find("th:not(.k-group-cell,.k-hierarchy-cell)").index(cell);
                var prevCells = cell.prevAll(":not(.k-group-cell,.k-hierarchy-cell)");

                var idx;

                prevCells = prevCells.filter(function () {
                    return !this.rowSpan || this.rowSpan === 1;
                });

                var offset = 0;

                for (idx = 0; idx < prevCells.length; idx++) {
                    offset += parseInt(prevCells.eq(idx).attr(colSpanAttr), 10) || 1;
                }

                var cells = child.find("th:not(.k-group-cell,.k-hierarchy-cell)");
                var colSpan = parseInt(cell.attr(colSpanAttr), 10) || 1;

                idx = 0;

                while (idx < colSpan) {
                    child = cells.eq(idx + offset);
                    result = result.add(childColumnsCells(child));
                    var value = parseInt(child.attr(colSpanAttr), 10);
                    if (value > 1) {
                        colSpan -= value - 1;
                    }
                    idx++;
                }
            }

            return result;
        }

        function appendContent(tbody, table, html) {
            var placeholder,
            tmp = tbody;

            if (tbodySupportsInnerHtml) {
                tbody[0].innerHTML = html;
            } else {
                placeholder = document.createElement("div");
                placeholder.innerHTML = "<table><tbody>" + html + "</tbody></table>";
                tbody = placeholder.firstChild.firstChild;
                table[0].replaceChild(tbody, tmp[0]);
                tbody = $(tbody);
            }
            return tbody;
        }

        function addHiddenStyle(attr) {
            attr = attr || {};
            var style = attr.style;

            if (!style) {
                style = "display:none";
            } else {
                style = style.replace(/((.*)?display)(.*)?:([^;]*)/i, "$1:none");
                if (style === attr.style) {
                    style = style.replace(/(.*)?/i, "display:none;$1");
                }
            }

            return extend({}, attr, { style: style });
        }

        function removeHiddenStyle(attr) {
            attr = attr || {};
            var style = attr.style;

            if (style) {
                attr.style = style.replace(/(display\s*:\s*none\s*;?)*/ig, "");
            }

            return attr;
        }

        function normalizeCols(table, visibleColumns, hasDetails, groups) {
            var colgroup = table.find(">colgroup"),
            width,
            cols = map(visibleColumns, function (column) {
                width = column.width;
                if (width && parseInt(width, 10) !== 0) {
                    return kendo.format('<col style="width:{0}"/>', typeof width === STRING ? width : width + "px");
                }

                return "<col />";
            });

            if (hasDetails || colgroup.find(".k-hierarchy-col").length) {
                cols.splice(0, 0, '<col class="k-hierarchy-col" />');
            }

            if (colgroup.length) {
                colgroup.remove();
            }

            colgroup = $(new Array(groups + 1).join('<col class="k-group-col">') + cols.join(""));
            if (!colgroup.is("colgroup")) {
                colgroup = $("<colgroup/>").append(colgroup);
            }

            table.prepend(colgroup);

            // fill gap after column hiding
            if (browser.msie && browser.version == 8) {
                table.css("display", "inline-table");
                window.setTimeout(function () { table.css("display", ""); }, 1);
            }
        }

        function normalizeHeaderCells(th, columns) {
            var lastIndex = 0;
            var idx, len;

            for (idx = 0, len = columns.length; idx < len; idx++) {
                if (columns[idx].locked) {
                    th.eq(idx).insertBefore(th.eq(lastIndex));
                    lastIndex++;
                }
            }
        }

        function convertToObject(array) {
            var result = {},
            item,
            idx,
            length;

            for (idx = 0, length = array.length; idx < length; idx++) {
                item = array[idx];
                result[item.value] = item.text;
            }

            return result;
        }

        function formatGroupValue(value, format, columnValues) {
            var isForiegnKey = columnValues && columnValues.length && isPlainObject(columnValues[0]) && "value" in columnValues[0],
            groupValue = isForiegnKey ? convertToObject(columnValues)[value] : value;

            groupValue = groupValue != null ? groupValue : "";

            return format ? kendo.format(format, groupValue) : groupValue;
        }

        function setCellVisibility(cells, index, visible) {
            var pad = 0,
            state,
            cell = cells[pad];

            while (cell) {
                state = visible ? true : cell.style.display !== "none";

                if (state && !nonDataCellsRegExp.test(cell.className) && --index < 0) {
                    cell.style.display = visible ? "" : "none";
                    break;
                }

                cell = cells[++pad];
            }
        }

        function hideColumnCells(rows, columnIndex) {
            var idx = 0,
            length = rows.length,
            cell, row;

            for (; idx < length; idx += 1) {
                row = rows.eq(idx);
                if (row.is(".k-grouping-row,.k-detail-row")) {
                    cell = row.children(":not(.k-group-cell):first,.k-detail-cell").last();
                    cell.attr("colspan", parseInt(cell.attr("colspan"), 10) - 1);
                } else {
                    if (row.hasClass("k-grid-edit-row") && (cell = row.children(".k-edit-container")[0])) {
                        cell = $(cell);
                        cell.attr("colspan", parseInt(cell.attr("colspan"), 10) - 1);
                        cell.find("col").eq(columnIndex).remove();
                        row = cell.find("tr:first");
                    }

                    setCellVisibility(row[0].cells, columnIndex, false);
                }
            }
        }

        function showColumnCells(rows, columnIndex) {
            var idx = 0,
            length = rows.length,
            cell, row, columns;

            for (; idx < length; idx += 1) {
                row = rows.eq(idx);
                if (row.is(".k-grouping-row,.k-detail-row")) {
                    cell = row.children(":not(.k-group-cell):first,.k-detail-cell").last();
                    cell.attr("colspan", parseInt(cell.attr("colspan"), 10) + 1);
                } else {
                    if (row.hasClass("k-grid-edit-row") && (cell = row.children(".k-edit-container")[0])) {
                        cell = $(cell);
                        cell.attr("colspan", parseInt(cell.attr("colspan"), 10) + 1);
                        normalizeCols(cell.find(">form>table"), visibleColumns(columns), false, 0);
                        row = cell.find("tr:first");
                    }

                    setCellVisibility(row[0].cells, columnIndex, true);
                }
            }
        }

        function updateColspan(toAdd, toRemove, num) {
            num = num || 1;

            var item, idx, length;
            for (idx = 0, length = toAdd.length; idx < length; idx++) {
                item = toAdd.eq(idx).children().last();
                item.attr("colspan", parseInt(item.attr("colspan"), 10) + num);

                item = toRemove.eq(idx).children().last();
                item.attr("colspan", parseInt(item.attr("colspan"), 10) - num);
            }
        }

        function tableWidth(table) {
            var idx, length, width = 0;
            var cols = table.find(">colgroup>col");

            for (idx = 0, length = cols.length; idx < length; idx += 1) {
                width += parseInt(cols[idx].style.width, 10);
            }

            return width;
        }

        var Grid = kendo.ui.DataBoundWidget.extend({
            init: function (element, options, events) {
                var that = this;

                options = isArray(options) ? { dataSource: options} : options;

                Widget.fn.init.call(that, element, options);

                if (events) {
                    that._events = events;
                }

                isRtl = kendo.support.isRtl(element);

                that._element();

                that._aria();

                that._columns(that.options.columns);

                that._dataSource();

                that._tbody();

                that._pageable();

                that._thead();

                that._groupable();

                that._toolbar();

                that._setContentHeight();

                that._templates();

                that._navigatable();

                that._selectable();

                that._details();

                that._editable();

                that._attachCustomCommandsEvent();

                if (that.options.autoBind) {
                    that.dataSource.fetch();
                } else {
                    that._footer();
                }

                if (that.lockedContent) {
                    that.wrapper.addClass("k-grid-lockedcolumns");
                    that._resizeHandler = function () { that.resize(); };
                    $(window).on("resize" + NS, that._resizeHandler);
                }

                kendo.notify(that);
            },

            events: [
           CHANGE,
           "dataBinding",
           "cancel",
           "pdfExport",
           DATABOUND,
           DETAILEXPAND,
           DETAILCOLLAPSE,
           DETAILINIT,
           FILTERMENUINIT,
           COLUMNMENUINIT,
           EDIT,
           SAVE,
           REMOVE,
           SAVECHANGES,
           COLUMNRESIZE,
           COLUMNREORDER,
           COLUMNSHOW,
           COLUMNHIDE,
           COLUMNLOCK,
           COLUMNUNLOCK
        ],

            setDataSource: function (dataSource) {
                var that = this;
                var scrollable = that.options.scrollable;

                that.options.dataSource = dataSource;

                that._dataSource();

                that._pageable();

                that._thead();

                if (scrollable) {
                    if (scrollable.virtual) {
                        that.content.find(">.k-virtual-scrollable-wrap").scrollLeft(0);
                    } else {
                        that.content.scrollLeft(0);
                    }
                }

                if (that.options.groupable) {
                    that._groupable();
                }

                if (that.virtualScrollable) {
                    that.virtualScrollable.setDataSource(that.options.dataSource);
                }

                if (that.options.navigatable) {
                    that._navigatable();
                }

                if (that.options.selectable) {
                    that._selectable();
                }

                if (that.options.autoBind) {
                    dataSource.fetch();
                }
            },

            options: {
                name: "Grid",
                columns: [],
                toolbar: null,
                autoBind: true,
                filterable: false,
                scrollable: true,
                sortable: false,
                selectable: false,
                navigatable: false,
                pageable: false,
                editable: false,
                groupable: false,
                rowTemplate: "",
                altRowTemplate: "",
                dataSource: {},
                height: null,
                resizable: false,
                reorderable: false,
                columnMenu: false,
                detailTemplate: null,
                columnResizeHandleWidth: 3,
                mobile: "",
                messages: {
                    editable: {
                        cancelDelete: CANCELDELETE,
                        confirmation: DELETECONFIRM,
                        confirmDelete: CONFIRMDELETE
                    },
                    commands: {
                        create: defaultCommands.create.text,
                        cancel: defaultCommands.cancel.text,
                        save: defaultCommands.save.text,
                        destroy: defaultCommands.destroy.text,
                        edit: defaultCommands.edit.text,
                        update: defaultCommands.update.text,
                        canceledit: defaultCommands.canceledit.text,
                        excel: defaultCommands.excel.text,
                        pdf: defaultCommands.pdf.text
                    }
                }
            },

            destroy: function () {
                var that = this,
                element;

                Widget.fn.destroy.call(that);

                if (that._resizeHandler) {
                    $(window).off("resize" + NS, that._resizeHandler);
                }

                if (that.pager && that.pager.element) {
                    that.pager.destroy();
                }

                that.pager = null;

                if (that.groupable && that.groupable.element) {
                    that.groupable.element.kendoGroupable("destroy");
                }

                that.groupable = null;

                if (that.options.reorderable) {
                    that.wrapper.data("kendoReorderable").destroy();
                }

                if (that.selectable && that.selectable.element) {
                    that.selectable.destroy();
                }

                that.selectable = null;

                if (that.resizable) {
                    that.resizable.destroy();

                    if (that._resizeUserEvents) {
                        if (that._resizeHandleDocumentClickHandler) {
                            $(document).off("click", that._resizeHandleDocumentClickHandler);
                        }
                        that._resizeUserEvents.destroy();
                        that._resizeUserEvents = null;
                    }
                    that.resizable = null;
                }

                if (that.virtualScrollable && that.virtualScrollable.element) {
                    that.virtualScrollable.destroy();
                }

                that.virtualScrollable = null;

                that._destroyColumnAttachments();

                that._destroyEditable();

                if (that.dataSource) {
                    that.dataSource.unbind(CHANGE, that._refreshHandler)
                           .unbind(PROGRESS, that._progressHandler)
                           .unbind(ERROR, that._errorHandler);

                    that._refreshHandler = that._progressHandler = that._errorHandler = null;
                }

                element = that.element
                .add(that.wrapper)
                .add(that.table)
                .add(that.thead)
                .add(that.wrapper.find(">.k-grid-toolbar"));

                if (that.content) {
                    element = element
                        .add(that.content)
                        .add(that.content.find(">.k-virtual-scrollable-wrap"));
                }

                if (that.lockedHeader) {
                    that._removeLockedContainers();
                }

                if (that.pane) {
                    that.pane.destroy();
                }

                if (that._draggableInstance && that._draggableInstance.element) {
                    that._draggableInstance.destroy();
                }

                that._draggableInstance = null;

                element.off(NS);

                kendo.destroy(that.wrapper);

                that.scrollables =
            that.thead =
            that.tbody =
            that.element =
            that.table =
            that.content =
            that.footer =
            that.wrapper =
            that._groupableClickHandler =
            that._setContentWidthHandler = null;
            },

            getOptions: function () {
                var result = extend(true, {}, this.options);
                result.columns = kendo.deepExtend([], this.columns);
                var dataSource = this.dataSource;

                result.dataSource = $.extend(true, {}, dataSource.options);

                result.dataSource.page = dataSource.page();
                result.dataSource.filter = dataSource.filter();
                result.dataSource.pageSize = dataSource.pageSize();
                result.dataSource.sort = dataSource.sort();
                result.dataSource.group = dataSource.group();
                result.dataSource.aggregate = dataSource.aggregate();

                result.$angular = undefined;

                return result;
            },

            setOptions: function (options) {
                var currentOptions = this.getOptions();
                kendo.deepExtend(currentOptions, options);
                var wrapper = this.wrapper;
                var events = this._events;
                var element = this.element;

                this.destroy();
                this.options = null;
                if (wrapper[0] !== element[0]) {
                    wrapper.before(element);
                    wrapper.remove();
                }
                element.empty();

                this.init(element, currentOptions, events);
                this._setEvents(currentOptions);

            },

            items: function () {
                if (this.lockedContent) {
                    return this._items(this.tbody).add(this._items(this.lockedTable.children("tbody")));
                } else {
                    return this._items(this.tbody);
                }
            },

            _items: function (container) {
                return container.children().filter(function () {
                    var tr = $(this);
                    return !tr.hasClass("k-grouping-row") && !tr.hasClass("k-detail-row") && !tr.hasClass("k-group-footer");
                });
            },

            dataItems: function () {
                var dataItems = kendo.ui.DataBoundWidget.fn.dataItems.call(this);
                if (this.lockedContent) {
                    var n = dataItems.length, tmp = new Array(2 * n);
                    for (var i = n; --i >= 0; ) {
                        tmp[i] = tmp[i + n] = dataItems[i];
                    }
                    dataItems = tmp;
                }

                return dataItems;
            },

            _destroyColumnAttachments: function () {
                var that = this;

                that.resizeHandle = null;

                if (!that.thead) {
                    return;
                }

                this.angular("cleanup", function () {
                    return { elements: that.thead.get() };
                });

                that.thead.find("th").each(function () {
                    var th = $(this),
                    filterMenu = th.data("kendoFilterMenu"),
                    sortable = th.data("kendoColumnSorter"),
                    columnMenu = th.data("kendoColumnMenu");

                    if (filterMenu) {
                        filterMenu.destroy();
                    }

                    if (sortable) {
                        sortable.destroy();
                    }

                    if (columnMenu) {
                        columnMenu.destroy();
                    }
                });
            },

            _attachCustomCommandsEvent: function () {
                var that = this,
                columns = leafColumns(that.columns || []),
                command,
                idx,
                length;

                for (idx = 0, length = columns.length; idx < length; idx++) {
                    command = columns[idx].command;

                    if (command) {
                        attachCustomCommandEvent(that, that.wrapper, command);
                    }
                }
            },

            _aria: function () {
                var id = this.element.attr("id") || "aria";

                if (id) {
                    this._cellId = id + "_active_cell";
                }
            },

            _element: function () {
                var that = this,
                table = that.element;

                if (!table.is("table")) {
                    if (that.options.scrollable) {
                        table = that.element.find("> .k-grid-content > table");
                    } else {
                        table = that.element.children("table");
                    }

                    if (!table.length) {
                        table = $("<table />").appendTo(that.element);
                    }
                }

                if (isIE7) {
                    table.attr("cellspacing", 0);
                }

                that.table = table.attr("role", that._hasDetails() ? "treegrid" : "grid");

                that._wrapper();
            },

            _createResizeHandle: function (container, th) {
                var that = this;
                var indicatorWidth = that.options.columnResizeHandleWidth;
                var scrollable = that.options.scrollable;
                var resizeHandle = that.resizeHandle;
                var groups = this._groups();
                var left;

                if (resizeHandle && that.lockedContent && resizeHandle.data("th")[0] !== th[0]) {
                    resizeHandle.remove();
                    resizeHandle = null;
                }

                if (!resizeHandle) {
                    resizeHandle = that.resizeHandle = $('<div class="k-resize-handle"><div class="k-resize-handle-inner"></div></div>');
                    container.append(resizeHandle);
                }

                if (!isRtl) {
                    left = th[0].offsetWidth;

                    var cells = leafDataCells(th.closest("thead"));
                    for (var idx = 0; idx < cells.length; idx++) {
                        if (cells[idx] == th[0]) {
                            break;
                        }
                        left += cells[idx].offsetWidth;
                    }

                    if (groups > 0) {
                        left += container.find(".k-group-cell:first").outerWidth() * groups;
                    }

                } else {
                    left = th.position().left;
                    if (scrollable) {
                        var headerWrap = th.closest(".k-grid-header-wrap, .k-grid-header-locked"),
                        ieCorrection = browser.msie ? headerWrap.scrollLeft() : 0,
                        webkitCorrection = browser.webkit ? (headerWrap[0].scrollWidth - headerWrap[0].offsetWidth - headerWrap.scrollLeft()) : 0,
                        firefoxCorrection = browser.mozilla ? (headerWrap[0].scrollWidth - headerWrap[0].offsetWidth - (headerWrap[0].scrollWidth - headerWrap[0].offsetWidth - headerWrap.scrollLeft())) : 0;

                        left -= webkitCorrection - firefoxCorrection + ieCorrection;
                    }
                }

                resizeHandle.css({
                    top: th.position().top, //scrollable ? 0 : heightAboveHeader(that.wrapper),
                    left: left - indicatorWidth,
                    height: th.outerHeight(),
                    width: indicatorWidth * 3
                })
            .data("th", th)
            .show();
            },

            _positionColumnResizeHandle: function (container) {
                var that = this,
                indicatorWidth = that.options.columnResizeHandleWidth,
                lockedHead = that.lockedHeader ? that.lockedHeader.find("thead:first") : $();

                that.thead.add(lockedHead).on("mousemove" + NS, "th", function (e) {
                    var th = $(this);

                    if (th.hasClass("k-group-cell") || th.hasClass("k-hierarchy-cell")) {
                        return;
                    }

                    var clientX = e.clientX,
                    winScrollLeft = $(window).scrollLeft(),
                    position = th.offset().left + (!isRtl ? this.offsetWidth : 0);

                    if (clientX + winScrollLeft > position - indicatorWidth && clientX + winScrollLeft < position + indicatorWidth) {
                        that._createResizeHandle(th.closest("div"), th);
                    } else if (that.resizeHandle) {
                        that.resizeHandle.hide();
                    } else {
                        cursor(that.wrapper, "");
                    }
                });
            },

            _resizeHandleDocumentClick: function (e) {
                if ($(e.target).closest(".k-column-active").length) {
                    return;
                }

                $(document).off(e);

                this._hideResizeHandle();
            },

            _hideResizeHandle: function () {
                if (this.resizeHandle) {
                    this.resizeHandle.data("th")
                    .removeClass("k-column-active");

                    if (this.lockedContent && !this._isMobile) {
                        this.resizeHandle.remove();
                        this.resizeHandle = null;
                    } else {
                        this.resizeHandle.hide();
                    }
                }
            },

            _positionColumnResizeHandleTouch: function (container) {
                var that = this,
                lockedHead = that.lockedHeader ? that.lockedHeader.find("thead:first") : $();

                that._resizeUserEvents = new kendo.UserEvents(lockedHead.add(that.thead), {
                    filter: "th:not(.k-group-cell):not(.k-hierarchy-cell)",
                    threshold: 10,
                    hold: function (e) {
                        var th = $(e.target);

                        e.preventDefault();

                        th.addClass("k-column-active");
                        that._createResizeHandle(th.closest("div"), th);

                        if (!that._resizeHandleDocumentClickHandler) {
                            that._resizeHandleDocumentClickHandler = proxy(that._resizeHandleDocumentClick, that);
                        }

                        $(document).on("click", that._resizeHandleDocumentClickHandler);
                    }
                });
            },

            _resizable: function () {
                var that = this,
                options = that.options,
                container,
                columnStart,
                columnWidth,
                gridWidth,
                isMobile = this._isMobile,
                scrollbar = !kendo.support.mobileOS ? kendo.support.scrollbar() : 0,
                isLocked,
                col, th;

                if (options.resizable) {
                    container = options.scrollable ? that.wrapper.find(".k-grid-header-wrap:first") : that.wrapper;

                    if (isMobile) {
                        that._positionColumnResizeHandleTouch(container);
                    } else {
                        that._positionColumnResizeHandle(container);
                    }

                    if (that.resizable) {
                        that.resizable.destroy();
                    }

                    that.resizable = new ui.Resizable(container.add(that.lockedHeader), {
                        handle: ".k-resize-handle",
                        hint: function (handle) {
                            return $('<div class="k-grid-resize-indicator" />').css({
                                height: handle.data("th").outerHeight() + that.tbody.attr("clientHeight")
                            });
                        },
                        start: function (e) {
                            th = $(e.currentTarget).data("th");

                            if (isMobile) {
                                that._hideResizeHandle();
                            }

                            var header = th.closest("table"),
                            index = $.inArray(th[0], leafDataCells(th.closest("thead")).filter(":visible"));

                            isLocked = header.parent().hasClass("k-grid-header-locked");

                            var contentTable = isLocked ? that.lockedTable : that.table,
                            footer = that.footer || $();

                            if (that.footer && that.lockedContent) {
                                footer = isLocked ? that.footer.children(".k-grid-footer-locked") : that.footer.children(".k-grid-footer-wrap");
                            }

                            cursor(that.wrapper, 'col-resize');

                            if (options.scrollable) {
                                col = header.find("col:not(.k-group-col):not(.k-hierarchy-col):eq(" + index + ")")
                                .add(contentTable.children("colgroup").find("col:not(.k-group-col):not(.k-hierarchy-col):eq(" + index + ")"))
                                .add(footer.find("colgroup").find("col:not(.k-group-col):not(.k-hierarchy-col):eq(" + index + ")"));
                            } else {
                                col = contentTable.children("colgroup").find("col:not(.k-group-col):not(.k-hierarchy-col):eq(" + index + ")");
                            }

                            columnStart = e.x.location;
                            columnWidth = th.outerWidth();
                            gridWidth = isLocked ? contentTable.children("tbody").outerWidth() : that.tbody.outerWidth(); // IE returns 0 if grid is empty and scrolling is enabled
                        },
                        resize: function (e) {
                            var rtlMultiplier = isRtl ? -1 : 1,
                            currentWidth = columnWidth + (e.x.location * rtlMultiplier) - (columnStart * rtlMultiplier);

                            if (options.scrollable) {
                                var footer;
                                if (isLocked && that.lockedFooter) {
                                    footer = that.lockedFooter.children("table");
                                } else if (that.footer) {
                                    footer = that.footer.find(">.k-grid-footer-wrap>table");
                                }
                                if (!footer || !footer[0]) {
                                    footer = $();
                                }
                                var header = th.closest("table");
                                var contentTable = isLocked ? that.lockedTable : that.table;
                                var constrain = false;
                                var totalWidth = that.wrapper.width() - scrollbar;
                                var width = currentWidth;

                                if (isLocked && gridWidth - columnWidth + width > totalWidth) {
                                    width = columnWidth + (totalWidth - gridWidth - scrollbar * 2);
                                    if (width < 0) {
                                        width = currentWidth;
                                    }
                                    constrain = true;
                                }

                                if (width > 10) {
                                    col.css('width', width);

                                    if (gridWidth) {
                                        if (constrain) {
                                            width = totalWidth - scrollbar * 2;
                                        } else {
                                            width = gridWidth + (e.x.location * rtlMultiplier) - (columnStart * rtlMultiplier);
                                        }

                                        contentTable
                                        .add(header)
                                        .add(footer)
                                        .css('width', width);

                                        if (!isLocked) {
                                            that._footerWidth = width;
                                        }
                                    }
                                }
                            } else if (currentWidth > 10) {
                                col.css('width', currentWidth);
                            }
                        },
                        resizeend: function () {
                            var newWidth = th.outerWidth(),
                            column,
                            header;

                            cursor(that.wrapper, "");

                            if (columnWidth != newWidth) {
                                header = that.lockedHeader ? that.lockedHeader.find("thead:first tr:first").add(that.thead.find("tr:first")) : th.parent();

                                var index = th.attr(kendo.attr("index"));
                                if (!index) {
                                    index = header.find("th:not(.k-group-cell):not(.k-hierarchy-cell)").index(th);
                                }
                                column = leafColumns(that.columns)[index];

                                column.width = newWidth;

                                that.trigger(COLUMNRESIZE, {
                                    column: column,
                                    oldWidth: columnWidth,
                                    newWidth: newWidth
                                });

                                that._applyLockedContainersWidth();
                                that._syncLockedContentHeight();
                                that._syncLockedHeaderHeight();
                            }

                            that._hideResizeHandle();
                            th = null;
                        }
                    });

                }
            },

            _draggable: function () {
                var that = this;
                if (that.options.reorderable) {
                    if (that._draggableInstance) {
                        that._draggableInstance.destroy();
                    }

                    that._draggableInstance = that.wrapper.kendoDraggable({
                        group: kendo.guid(),
                        filter: that.content ? ".k-grid-header:first " + HEADERCELLS : "table:first>.k-grid-header " + HEADERCELLS,
                        drag: function () {
                            that._hideResizeHandle();
                        },
                        hint: function (target) {
                            return $('<div class="k-header k-drag-clue" />')
                            .css({
                                width: target.width(),
                                paddingLeft: target.css("paddingLeft"),
                                paddingRight: target.css("paddingRight"),
                                lineHeight: target.height() + "px",
                                paddingTop: target.css("paddingTop"),
                                paddingBottom: target.css("paddingBottom")
                            })
                            .html(target.attr(kendo.attr("title")) || target.attr(kendo.attr("field")) || target.text())
                            .prepend('<span class="k-icon k-drag-status k-denied" />');
                        }
                    }).data("kendoDraggable");
                }
            },

            _reorderable: function () {
                var that = this;
                if (that.options.reorderable) {
                    if (that.wrapper.data("kendoReorderable")) {
                        that.wrapper.data("kendoReorderable").destroy();
                    }

                    var targetParentContainerIndex = function (columns, sourceIndex, targetIndex) {
                        var column = columns[sourceIndex];
                        var target = columns[targetIndex];

                        var parent = columnParent(column, that.columns);
                        columns = parent ? parent.columns : that.columns;

                        return inArray(target, columns);
                    };

                    that.wrapper.kendoReorderable({
                        draggable: that._draggableInstance,
                        dragOverContainers: function (sourceIndex, targetIndex) {
                            var columns = flatColumnsInDomOrder(that.columns);
                            return columns[sourceIndex].lockable !== false && targetParentContainerIndex(columns, sourceIndex, targetIndex) > -1;
                        },
                        inSameContainer: function (e) {
                            return $(e.source).parent()[0] === $(e.target).parent()[0] && targetParentContainerIndex(flatColumnsInDomOrder(that.columns), e.sourceIndex, e.targetIndex) > -1;
                        },
                        change: function (e) {
                            var columns = flatColumnsInDomOrder(that.columns);
                            var column = columns[e.oldIndex];
                            var newIndex = targetParentContainerIndex(columns, e.oldIndex, e.newIndex);

                            that.trigger(COLUMNREORDER, {
                                newIndex: newIndex,
                                oldIndex: inArray(column, columns),
                                column: column
                            });

                            that.reorderColumn(newIndex, column, e.position === "before");
                        }
                    });
                }
            },

            _reorderHeader: function (sources, target, before) {
                var that = this;
                var sourcePosition = columnVisiblePosition(sources[0], that.columns);
                var destPosition = columnVisiblePosition(target, that.columns);

                var leafs = [];
                for (var idx = 0; idx < sources.length; idx++) {
                    if (sources[idx].columns) {
                        leafs = leafs.concat(sources[idx].columns);
                    }
                }

                var ths = elements(that.lockedHeader, that.thead, "tr:eq(" + sourcePosition.row + ")>th.k-header:not(.k-group-cell,.k-hierarchy-cell)");

                var sourceLockedColumns = lockedColumns(sources).length;
                var targetLockedColumns = lockedColumns([target]).length;

                if (leafs.length) {
                    if (sourceLockedColumns > 0 && targetLockedColumns === 0) {
                        moveCellsBetweenContainers(sources, target, leafs, that.columns, that.lockedHeader.find("thead"), that.thead, this._groups());
                    } else if (sourceLockedColumns === 0 && targetLockedColumns > 0) {
                        moveCellsBetweenContainers(sources, target, leafs, that.columns, that.thead, that.lockedHeader.find("thead"), this._groups());
                    }

                    target = findReorderTarget(that.columns, target, sources[0], before);
                    if (target) {
                        that._reorderHeader(leafs, target, before);
                    }
                } else if (sourceLockedColumns !== targetLockedColumns) { // move between containers
                    updateCellRowSpan(ths[sourcePosition.cell], that.columns, sourceLockedColumns);
                }

                reorder(ths, sourcePosition.cell, destPosition.cell, before, sources.length);
            },

            _reorderContent: function (sources, destination, before) {
                var that = this;
                var lockedRows = $();
                var source = sources[0];
                var sourceIndex = inArray(source, leafColumns(that.columns));
                var destIndex = inArray(destination, leafColumns(that.columns));

                var colSourceIndex = inArray(source, visibleLeafColumns(that.columns));
                var colDest = inArray(destination, visibleLeafColumns(that.columns));
                var lockedCount = lockedColumns(that.columns).length;
                var isLocked = !!destination.locked;
                var footer = that.footer || that.wrapper.find(".k-grid-footer");

                var headerCol, footerCol;
                headerCol = footerCol = colDest;

                if (destination.hidden) {
                    if (isLocked) {
                        colDest = that.lockedTable.find("colgroup");
                        headerCol = that.lockedHeader.find("colgroup");
                        footerCol = $(that.lockedFooter).find(">table>colgroup");
                    } else {
                        colDest = that.tbody.prev();
                        headerCol = that.thead.prev();
                        footerCol = footer.find(".k-grid-footer-wrap").find(">table>colgroup");
                    }
                }

                if (that._hasFilterRow()) {
                    reorder(that.wrapper.find(".k-filter-row th:not(.k-group-cell,.k-hierarchy-cell)"), sourceIndex, destIndex, before, sources.length);
                }

                reorder(elements(that.lockedHeader, that.thead.prev(), "col:not(.k-group-col,.k-hierarchy-col)"), colSourceIndex, headerCol, before, sources.length);

                if (that.options.scrollable) {
                    reorder(elements(that.lockedTable, that.tbody.prev(), "col:not(.k-group-col,.k-hierarchy-col)"), colSourceIndex, colDest, before, sources.length);
                }

                if (footer && footer.length) {
                    reorder(elements(that.lockedFooter, footer.find(".k-grid-footer-wrap"), ">table>colgroup>col:not(.k-group-col,.k-hierarchy-col)"), colSourceIndex, footerCol, before, sources.length);
                    reorder(footer.find(".k-footer-template>td:not(.k-group-cell,.k-hierarchy-cell)"), sourceIndex, destIndex, before, sources.length);
                }

                var rows = that.tbody.children(":not(.k-grouping-row,.k-detail-row)");
                if (that.lockedTable) {
                    if (lockedCount > destIndex) {
                        if (lockedCount <= sourceIndex) {
                            updateColspan(
                            that.lockedTable.find(">tbody>tr.k-grouping-row"),
                            that.table.find(">tbody>tr.k-grouping-row"),
                            sources.length
                        );
                        }
                    } else if (lockedCount > sourceIndex) {
                        updateColspan(
                        that.table.find(">tbody>tr.k-grouping-row"),
                        that.lockedTable.find(">tbody>tr.k-grouping-row"),
                        sources.length
                    );
                    }

                    lockedRows = that.lockedTable.find(">tbody>tr:not(.k-grouping-row,.k-detail-row)");
                }

                for (var idx = 0, length = rows.length; idx < length; idx += 1) {
                    reorder(elements(lockedRows[idx], rows[idx], ">td:not(.k-group-cell,.k-hierarchy-cell)"), sourceIndex, destIndex, before, sources.length);
                }
            },

            reorderColumn: function (destIndex, column, before) {
                var that = this,
                parent = columnParent(column, that.columns),
                columns = parent ? parent.columns : that.columns,
                sourceIndex = inArray(column, columns),
                destColumn = columns[destIndex],
                lockChanged,
                isLocked = !!destColumn.locked,
                lockedCount = lockedColumns(that.columns).length;

                if (sourceIndex === destIndex) {
                    return;
                }

                if (!column.locked && isLocked && nonLockedColumns(that.columns).length == 1) {
                    return;
                }

                if (column.locked && !isLocked && lockedCount == 1) {
                    return;
                }

                that._hideResizeHandle();

                if (before === undefined) {
                    before = destIndex < sourceIndex;
                }

                var sourceColumns = [column];

                that._reorderHeader(sourceColumns, destColumn, before);

                if (that.lockedHeader) {
                    removeEmptyRows(that.thead);
                    removeEmptyRows(that.lockedHeader);
                }

                if (destColumn.columns) {
                    destColumn = leafColumns(destColumn.columns);
                    destColumn = destColumn[before ? 0 : destColumn.length - 1];
                }

                if (column.columns) {
                    sourceColumns = leafColumns(column.columns);
                }

                that._reorderContent(sourceColumns, destColumn, before);

                lockChanged = !!column.locked;
                lockChanged = lockChanged != isLocked;
                column.locked = isLocked;

                columns.splice(before ? destIndex : destIndex + 1, 0, column);
                columns.splice(sourceIndex < destIndex ? sourceIndex : sourceIndex + 1, 1);

                that._templates();

                that._updateColumnCellIndex();

                that._updateTablesWidth();
                that._applyLockedContainersWidth();
                that._syncLockedHeaderHeight();
                that._syncLockedContentHeight();
                that._updateFirstColumnClass();

                if (!lockChanged) {
                    return;
                }

                if (isLocked) {
                    that.trigger(COLUMNLOCK, {
                        column: column
                    });
                } else {
                    that.trigger(COLUMNUNLOCK, {
                        column: column
                    });
                }
            },

            _updateColumnCellIndex: function () {
                var header;
                var offset = 0;

                if (this.lockedHeader) {
                    header = this.lockedHeader.find("thead");
                    offset = updateCellIndex(header, lockedColumns(this.columns));
                }
                updateCellIndex(this.thead, nonLockedColumns(this.columns), offset);
            },

            lockColumn: function (column) {
                var columns = this.columns;

                if (typeof column == "number") {
                    column = columns[column];
                } else {
                    column = grep(columns, function (item) {
                        return item.field === column;
                    })[0];
                }

                if (!column || column.locked || column.hidden) {
                    return;
                }

                var index = lockedColumns(columns).length - 1;
                this.reorderColumn(index, column, false);
            },

            unlockColumn: function (column) {
                var columns = this.columns;

                if (typeof column == "number") {
                    column = columns[column];
                } else {
                    column = grep(columns, function (item) {
                        return item.field === column;
                    })[0];
                }

                if (!column || !column.locked || column.hidden) {
                    return;
                }

                var index = lockedColumns(columns).length;
                this.reorderColumn(index, column, true);
            },

            cellIndex: function (td) {
                var lockedColumnOffset = 0;

                if (this.lockedTable && !$.contains(this.lockedTable[0], td[0])) {
                    lockedColumnOffset = leafColumns(lockedColumns(this.columns)).length;
                }

                return $(td).parent().children('td:not(.k-group-cell,.k-hierarchy-cell)').index(td) + lockedColumnOffset;
            },

            _modelForContainer: function (container) {
                container = $(container);

                if (!container.is("tr") && this._editMode() !== "popup") {
                    container = container.closest("tr");
                }

                var id = container.attr(kendo.attr("uid"));

                return this.dataSource.getByUid(id);
            },

            _editable: function () {
                var that = this,
                selectable = that.selectable && that.selectable.options.multiple,
                editable = that.options.editable,
                handler = function () {
                    var target = activeElement(),
                        cell = that._editContainer;

                    if (cell && !$.contains(cell[0], target) && cell[0] !== target && !$(target).closest(".k-animation-container").length) {
                        if (that.editable.end()) {
                            that.closeCell();
                        }
                    }
                };

                if (editable) {
                    var mode = that._editMode();
                    if (mode === "incell") {
                        if (editable.update !== false) {
                            that.wrapper.on(CLICK + NS, "tr:not(.k-grouping-row) > td", function (e) {
                                var td = $(this),
                                isLockedCell = that.lockedTable && td.closest("table")[0] === that.lockedTable[0];

                                if (td.hasClass("k-hierarchy-cell") ||
                                td.hasClass("k-detail-cell") ||
                                td.hasClass("k-group-cell") ||
                                td.hasClass("k-edit-cell") ||
                                td.has("a.k-grid-delete").length ||
                                td.has("button.k-grid-delete").length ||
                                (td.closest("tbody")[0] !== that.tbody[0] && !isLockedCell) ||
                                $(e.target).is(":input")) {
                                    return;
                                }

                                if (that.editable) {
                                    if (that.editable.end()) {
                                        if (selectable) {
                                            $(activeElement()).blur();
                                        }
                                        that.closeCell();
                                        that.editCell(td);
                                    }
                                } else {
                                    that.editCell(td);
                                }

                            })
                        .on("focusin" + NS, function () {
                            clearTimeout(that.timer);
                            that.timer = null;
                        })
                        .on("focusout" + NS, function () {
                            that.timer = setTimeout(handler, 1);
                        });
                        }
                    } else {
                        if (editable.update !== false) {
                            that.wrapper.on(CLICK + NS, "tbody>tr:not(.k-detail-row,.k-grouping-row):visible a.k-grid-edit", function (e) {
                                e.preventDefault();
                                that.editRow($(this).closest("tr"));
                            });
                        }
                    }

                    if (editable.destroy !== false) {
                        that.wrapper.on(CLICK + NS, "tbody>tr:not(.k-detail-row,.k-grouping-row):visible .k-grid-delete", function (e) {
                            e.preventDefault();
                            e.stopPropagation();
                            that.removeRow($(this).closest("tr"));
                        });
                    } else {
                        //Required for the MVC server wrapper delete button
                        that.wrapper.on(CLICK + NS, "tbody>tr:not(.k-detail-row,.k-grouping-row):visible button.k-grid-delete", function (e) {
                            e.stopPropagation();

                            if (!that._confirmation()) {
                                e.preventDefault();
                            }
                        });
                    }
                }
            },

            editCell: function (cell) {
                cell = $(cell);

                var that = this,
            column = leafColumns(that.columns)[that.cellIndex(cell)],
                model = that._modelForContainer(cell);

                that.closeCell();

                if (model && (!model.editable || model.editable(column.field)) && !column.command && column.field) {

                    that._attachModelChange(model);

                    that._editContainer = cell;

                    that.editable = cell.addClass("k-edit-cell")
                    .kendoEditable({
                        fields: { field: column.field, format: column.format, editor: column.editor, values: column.values },
                        model: model,
                        target: that,
                        change: function (e) {
                            if (that.trigger(SAVE, { values: e.values, container: cell, model: model })) {
                                e.preventDefault();
                            }
                        }
                    }).data("kendoEditable");

                    var tr = cell.parent().addClass("k-grid-edit-row");

                    if (that.lockedContent) {
                        adjustRowHeight(tr[0], that._relatedRow(tr).addClass("k-grid-edit-row")[0]);
                    }

                    that.trigger(EDIT, { container: cell, model: model });
                }
            },

            _adjustLockedHorizontalScrollBar: function () {
                var table = this.table,
                content = table.parent();

                var scrollbar = table[0].offsetWidth > content[0].clientWidth ? kendo.support.scrollbar() : 0;
                this.lockedContent.height(content.height() - scrollbar);
            },

            _syncLockedContentHeight: function () {
                if (this.lockedTable) {
                    this._adjustLockedHorizontalScrollBar();
                    this._adjustRowsHeight(this.table, this.lockedTable);
                }
            },

            _syncLockedHeaderHeight: function () {
                if (this.lockedHeader) {
                    var lockedTable = this.lockedHeader.children("table");
                    var table = this.thead.parent();

                    this._adjustRowsHeight(lockedTable, table);

                    syncTableHeight(lockedTable, table);
                }
            },

            _syncLockedFooterHeight: function () {
                if (this.lockedFooter && this.footer && this.footer.length) {
                    this._adjustRowsHeight(this.lockedFooter.children("table"), this.footer.find(".k-grid-footer-wrap > table"));
                }
            },

            _destroyEditable: function () {
                var that = this;

                var destroy = function () {
                    if (that.editable) {

                        var container = that.editView ? that.editView.element : that._editContainer;

                        if (container) {
                            container.off(CLICK + NS, "a.k-grid-cancel", that._editCancelClickHandler);
                            container.off(CLICK + NS, "a.k-grid-update", that._editUpdateClickHandler);
                        }

                        that._detachModelChange();
                        that.editable.destroy();
                        that.editable = null;
                        that._editContainer = null;
                        that._destroyEditView();
                    }
                };

                if (that.editable) {
                    if (that._editMode() === "popup" && !that._isMobile) {
                        that._editContainer.data("kendoWindow").bind("deactivate", destroy).close();
                    } else {
                        destroy();
                    }
                }
                if (that._actionSheet) {
                    that._actionSheet.destroy();
                    that._actionSheet = null;
                }
            },

            _destroyEditView: function () {
                if (this.editView) {
                    this.editView.purge();
                    this.editView = null;
                    this.pane.navigate("");
                }
            },

            _attachModelChange: function (model) {
                var that = this;

                that._modelChangeHandler = function (e) {
                    that._modelChange({ field: e.field, model: this });
                };

                model.bind("change", that._modelChangeHandler);
            },

            _detachModelChange: function () {
                var that = this,
                container = that._editContainer,
                model = that._modelForContainer(container);

                if (model) {
                    model.unbind(CHANGE, that._modelChangeHandler);
                }
            },

            closeCell: function (isCancel) {
                var that = this,
                cell = that._editContainer,
                id,
                column,
                tr,
                model;

                if (!cell) {
                    return;
                }

                id = cell.closest("tr").attr(kendo.attr("uid"));
                model = that.dataSource.getByUid(id);

                if (isCancel && that.trigger("cancel", { container: cell, model: model })) {
                    return;
                }

                cell.removeClass("k-edit-cell");
                column = leafColumns(that.columns)[that.cellIndex(cell)];

                tr = cell.parent().removeClass("k-grid-edit-row");

                that._destroyEditable(); // editable should be destroyed before content of the container is changed

                that._displayCell(cell, column, model);

                if (cell.hasClass("k-dirty-cell")) {
                    $('<span class="k-dirty"/>').prependTo(cell);
                }

                if (that.lockedContent) {
                    adjustRowHeight(tr.css("height", "")[0], that._relatedRow(tr).css("height", "")[0]);
                }
            },

            _displayCell: function (cell, column, dataItem) {
                var that = this,
                state = { storage: {}, count: 0 },
                settings = extend({}, kendo.Template, that.options.templateSettings),
                tmpl = kendo.template(that._cellTmpl(column, state), settings);

                if (state.count > 0) {
                    tmpl = proxy(tmpl, state.storage);
                }

                cell.empty().html(tmpl(dataItem));

                that.angular("compile", function () {
                    return {
                        elements: cell,
                        data: [{ dataItem: dataItem}]
                    };
                });
            },

            removeRow: function (row) {
                if (!this._confirmation(row)) {
                    return;
                }

                this._removeRow(row);
            },

            _removeRow: function (row) {
                var that = this,
                model,
                mode = that._editMode();

                if (mode !== "incell") {
                    that.cancelRow();
                }

                row = $(row).hide();
                model = that._modelForContainer(row);

                if (model && !that.trigger(REMOVE, { row: row, model: model })) {

                    that.dataSource.remove(model);

                    if (mode === "inline" || mode === "popup") {
                        that.dataSource.sync();
                    }
                } else if (mode === "incell") {
                    that._destroyEditable();
                }
            },

            _editMode: function () {
                var mode = "incell",
                editable = this.options.editable;

                if (editable !== true) {
                    if (typeof editable == "string") {
                        mode = editable;
                    } else {
                        mode = editable.mode || mode;
                    }
                }

                return mode;
            },

            editRow: function (row) {
                var model;
                var that = this;

                if (row instanceof kendo.data.ObservableObject) {
                    model = row;
                } else {
                    row = $(row);
                    model = that._modelForContainer(row);
                }

                var mode = that._editMode();
                var container;

                that.cancelRow();

                if (model) {

                    that._attachModelChange(model);

                    if (mode === "popup") {
                        that._createPopupEditor(model);
                    } else if (mode === "inline") {
                        that._createInlineEditor(row, model);
                    } else if (mode === "incell") {
                        $(row).children(DATA_CELL).each(function () {
                            var cell = $(this);
                            var column = leafColumns(that.columns)[cell.index()];

                            model = that._modelForContainer(cell);

                            if (model && (!model.editable || model.editable(column.field)) && column.field) {
                                that.editCell(cell);
                                return false;
                            }
                        });
                    }

                    container = that.editView ? that.editView.element : that._editContainer;

                    if (container) {
                        if (!this._editCancelClickHandler) {
                            this._editCancelClickHandler = proxy(this._editCancelClick, this);
                        }

                        container.on(CLICK + NS, "a.k-grid-cancel", this._editCancelClickHandler);

                        if (!this._editUpdateClickHandler) {
                            this._editUpdateClickHandler = proxy(this._editUpdateClick, this);
                        }

                        container.on(CLICK + NS, "a.k-grid-update", this._editUpdateClickHandler);
                    }
                }
            },

            _editUpdateClick: function (e) {
                e.preventDefault();
                e.stopPropagation();

                this.saveRow();
            },

            _editCancelClick: function (e) {
                var that = this;
                var navigatable = that.options.navigatable;
                var model = that.editable.options.model;
                var container = that.editView ? that.editView.element : that._editContainer;

                e.preventDefault();
                e.stopPropagation();

                if (that.trigger("cancel", { container: container, model: model })) {
                    return;
                }

                var currentIndex = that.items().index($(that.current()).parent());

                that.cancelRow();

                if (navigatable) {
                    that.current(that.items().eq(currentIndex).children().filter(NAVCELL).first());
                    focusTable(that.table, true);
                }
            },

            _createPopupEditor: function (model) {
                var that = this,
                html = '<div ' + kendo.attr("uid") + '="' + model.uid + '" class="k-popup-edit-form' + (that._isMobile ? ' k-mobile-list' : '') + '"><div class="k-edit-form-container">',
                column,
                command,
                fields = [],
                idx,
                length,
                tmpl,
                updateText,
                cancelText,
                tempCommand,
                columns = leafColumns(that.columns),
                attr,
                editable = that.options.editable,
                template = editable.template,
                options = isPlainObject(editable) ? editable.window : {},
                settings = extend({}, kendo.Template, that.options.templateSettings);

                options = options || {};

                if (template) {
                    if (typeof template === STRING) {
                        template = window.unescape(template);
                    }

                    html += (kendo.template(template, settings))(model);

                    for (idx = 0, length = columns.length; idx < length; idx++) {
                        column = columns[idx];
                        if (column.command) {
                            tempCommand = getCommand(column.command, "edit");
                            if (tempCommand) {
                                command = tempCommand;
                            }
                        }
                    }
                } else {
                    for (idx = 0, length = columns.length; idx < length; idx++) {
                        column = columns[idx];

                        if (!column.command) {
                            html += '<div class="k-edit-label"><label for="' + column.field + '">' + (column.title || column.field || "") + '</label></div>';

                            if ((!model.editable || model.editable(column.field)) && column.field) {
                                fields.push({ field: column.field, format: column.format, editor: column.editor, values: column.values });
                                html += '<div ' + kendo.attr("container-for") + '="' + column.field + '" class="k-edit-field"></div>';
                            } else {
                                var state = { storage: {}, count: 0 };

                                tmpl = kendo.template(that._cellTmpl(column, state), settings);

                                if (state.count > 0) {
                                    tmpl = proxy(tmpl, state.storage);
                                }

                                html += '<div class="k-edit-field">' + tmpl(model) + '</div>';
                            }
                        } else if (column.command) {
                            tempCommand = getCommand(column.command, "edit");
                            if (tempCommand) {
                                command = tempCommand;
                            }
                        }
                    }
                }

                if (command) {
                    if (isPlainObject(command)) {
                        if (command.text && isPlainObject(command.text)) {
                            updateText = command.text.update;
                            cancelText = command.text.cancel;
                        }

                        if (command.attr) {
                            attr = command.attr;
                        }
                    }
                }

                var container;

                if (!that._isMobile) {
                    html += '<div class="k-edit-buttons k-state-default">';
                    html += that._createButton({ name: "update", text: updateText, attr: attr }) + that._createButton({ name: "canceledit", text: cancelText, attr: attr });
                    html += '</div></div></div>';

                    container = that._editContainer = $(html)
                .appendTo(that.wrapper).eq(0)
                .kendoWindow(extend({
                    modal: true,
                    resizable: false,
                    draggable: true,
                    title: "Edit",
                    visible: false,
                    close: function (e) {
                        if (e.userTriggered) {
                            //The bellow line is required due to: draggable window in IE, change event will be triggered while the window is closing
                            e.sender.element.focus();
                            if (that.trigger("cancel", { container: container, model: model })) {
                                e.preventDefault();
                                return;
                            }

                            var currentIndex = that.items().index($(that.current()).parent());

                            that.cancelRow();
                            if (that.options.navigatable) {
                                that.current(that.items().eq(currentIndex).children().filter(NAVCELL).first());
                                focusTable(that.table, true);
                            }
                        }
                    }
                }, options));
                } else {
                    html += "</div></div>";
                    that.editView = that.pane.append(
                    '<div data-' + kendo.ns + 'role="view" data-' + kendo.ns + 'init-widgets="false" class="k-grid-edit-form">' +
                        '<div data-' + kendo.ns + 'role="header" class="k-header">' +
                            that._createButton({ name: "update", text: updateText, attr: attr }) +
                            (options.title || "Edit") +
                            that._createButton({ name: "canceledit", text: cancelText, attr: attr }) +
                        '</div>' +
                        html +
                    '</div>');
                    container = that._editContainer = that.editView.element.find(".k-popup-edit-form");
                }

                that.editable = that._editContainer
                .kendoEditable({
                    fields: fields,
                    model: model,
                    clearContainer: false,
                    target: that
                }).data("kendoEditable");

                // TODO: Replace this code with labels and for="ID"
                if (that._isMobile) {
                    container.find("input[type=checkbox],input[type=radio]")
                         .parent(".k-edit-field")
                         .addClass("k-check")
                         .prev(".k-edit-label")
                         .addClass("k-check")
                         .click(function () {
                             $(this).next().children("input").click();
                         });
                }

                that._openPopUpEditor();

                that.trigger(EDIT, { container: container, model: model });
            },

            _openPopUpEditor: function () {
                if (!this._isMobile) {
                    this._editContainer.data("kendoWindow").center().open();
                } else {
                    this.pane.navigate(this.editView, this._editAnimation);
                }
            },

            _createInlineEditor: function (row, model) {
                var that = this,
                column,
                cell,
                command,
                fields = [];


                if (that.lockedContent) {
                    row = row.add(that._relatedRow(row));
                }

                row.children(":not(.k-group-cell,.k-hierarchy-cell)").each(function () {
                    cell = $(this);
                    column = leafColumns(that.columns)[that.cellIndex(cell)];

                    if (!column.command && column.field && (!model.editable || model.editable(column.field))) {
                        fields.push({ field: column.field, format: column.format, editor: column.editor, values: column.values });
                        cell.attr(kendo.attr("container-for"), column.field);
                        cell.empty();
                    } else if (column.command) {
                        command = getCommand(column.command, "edit");
                        if (command) {
                            cell.empty();

                            var updateText,
                            cancelText,
                            attr;

                            if (isPlainObject(command)) {
                                if (command.text && isPlainObject(command.text)) {
                                    updateText = command.text.update;
                                    cancelText = command.text.cancel;
                                }

                                if (command.attr) {
                                    attr = command.attr;
                                }
                            }

                            $(that._createButton({ name: "update", text: updateText, attr: attr }) +
                            that._createButton({ name: "canceledit", text: cancelText, attr: attr })).appendTo(cell);
                        }
                    }
                });

                that._editContainer = row;

                that.editable = new kendo.ui.Editable(row
                .addClass("k-grid-edit-row"), {
                    target: that,
                    fields: fields,
                    model: model,
                    clearContainer: false
                });

                if (row.length > 1) {

                    adjustRowHeight(row[0], row[1]);
                    that._applyLockedContainersWidth();
                }

                that.trigger(EDIT, { container: row, model: model });
            },

            cancelRow: function () {
                var that = this,
                container = that._editContainer,
                model,
                tr;

                if (container) {
                    model = that._modelForContainer(container);

                    that._destroyEditable();

                    that.dataSource.cancelChanges(model);

                    if (that._editMode() !== "popup") {
                        that._displayRow(container);
                    } else {
                        that._displayRow(that.tbody.find("[" + kendo.attr("uid") + "=" + model.uid + "]"));
                    }
                }
            },

            saveRow: function () {
                var that = this,
                container = that._editContainer,
                model = that._modelForContainer(container),
                editable = that.editable;

                if (container && editable && editable.end() &&
                !that.trigger(SAVE, { container: container, model: model })) {

                    that.dataSource.sync();
                }
            },

            _displayRow: function (row) {
                var that = this,
                model = that._modelForContainer(row),
                related,
                newRow,
                nextRow,
                isSelected = row.hasClass("k-state-selected"),
                isAlt = row.hasClass("k-alt");

                if (model) {

                    if (that.lockedContent) {
                        related = $((isAlt ? that.lockedAltRowTemplate : that.lockedRowTemplate)(model));
                        that._relatedRow(row.last()).replaceWith(related);
                    }

                    that.angular("cleanup", function () { return { elements: row.get() }; });

                    newRow = $((isAlt ? that.altRowTemplate : that.rowTemplate)(model));
                    row.replaceWith(newRow);

                    that.angular("compile", function () {
                        return {
                            elements: newRow.get(),
                            data: [{ dataItem: model}]
                        };
                    });

                    if (isSelected && that.options.selectable) {
                        that.select(newRow.add(related));
                    }

                    if (related) {
                        adjustRowHeight(newRow[0], related[0]);
                    }

                    nextRow = newRow.next();
                    if (nextRow.hasClass("k-detail-row") && nextRow.is(":visible")) {
                        newRow.find(".k-hierarchy-cell .k-icon")
                        .removeClass("k-plus")
                        .addClass("k-minus");
                    }
                }
            },

            _showMessage: function (messages, row) {
                var that = this;

                if (!that._isMobile) {
                    return window.confirm(messages.title);
                }

                var template = kendo.template('<ul>' +
                '<li class="km-actionsheet-title">#:title#</li>' +
                '<li><a href="\\#" class="k-button k-grid-delete">#:confirmDelete#</a></li>' +
            '</ul>');

                var html = $(template(messages)).appendTo(that.view.element);

                var actionSheet = that._actionSheet = new kendo.mobile.ui.ActionSheet(html, {
                    cancel: messages.cancelDelete,
                    cancelTemplate: '<li class="km-actionsheet-cancel"><a class="k-button" href="\\#">#:cancel#</a></li>',
                    close: function () {
                        this.destroy();
                    },
                    command: function (e) {
                        var item = $(e.currentTarget).parent();
                        if (!item.hasClass("km-actionsheet-cancel")) {
                            that._removeRow(row);
                        }
                    },
                    popup: that._actionSheetPopupOptions
                });

                actionSheet.open(row);

                return false;
            },

            _confirmation: function (row) {
                var that = this,
                editable = that.options.editable,
                confirmation = editable === true || typeof editable === STRING ? that.options.messages.editable.confirmation : editable.confirmation;

                if (confirmation !== false && confirmation != null) {

                    if (typeof confirmation === FUNCTION) {
                        confirmation = confirmation(that._modelForContainer(row));
                    }

                    return that._showMessage({
                        confirmDelete: editable.confirmDelete || that.options.messages.editable.confirmDelete,
                        cancelDelete: editable.cancelDelete || that.options.messages.editable.cancelDelete,
                        title: confirmation === true ? that.options.messages.editable.confirmation : confirmation
                    }, row);
                }

                return true;
            },

            cancelChanges: function () {
                this.dataSource.cancelChanges();
            },

            saveChanges: function () {
                var that = this;

                if (((that.editable && that.editable.end()) || !that.editable) && !that.trigger(SAVECHANGES)) {
                    that.dataSource.sync();
                }
            },

            addRow: function () {
                var that = this,
                index,
                dataSource = that.dataSource,
                mode = that._editMode(),
                createAt = that.options.editable.createAt || "",
                pageSize = dataSource.pageSize(),
                view = dataSource.view() || [];

                if ((that.editable && that.editable.end()) || !that.editable) {
                    if (mode != "incell") {
                        that.cancelRow();
                    }

                    index = dataSource.indexOf(view[0]);

                    if (createAt.toLowerCase() == "bottom") {
                        index += view.length;

                        if (pageSize && !dataSource.options.serverPaging && pageSize <= view.length) {
                            index -= 1;
                        }
                    }

                    if (index < 0) {
                        if (dataSource.page() > dataSource.totalPages()) {
                            index = (dataSource.page() - 1) * pageSize;
                        } else {
                            index = 0;
                        }
                    }

                    var model = dataSource.insert(index, {}),
                    id = model.uid,
                    table = that.lockedContent ? that.lockedTable : that.table,
                    row = table.find("tr[" + kendo.attr("uid") + "=" + id + "]"),
                    cell = row.children("td:not(.k-group-cell,.k-hierarchy-cell)").eq(that._firstEditableColumnIndex(row));

                    if (mode === "inline" && row.length) {
                        that.editRow(row);
                    } else if (mode === "popup") {
                        that.editRow(model);
                    } else if (cell.length) {
                        that.editCell(cell);
                    }

                    if (createAt.toLowerCase() == "bottom" && that.lockedContent) {
                        //scroll the containers to the bottom
                        that.lockedContent[0].scrollTop = that.content[0].scrollTop = that.content[0].offsetHeight;
                    }
                }
            },

            _firstEditableColumnIndex: function (container) {
                var that = this,
                column,
                columns = leafColumns(that.columns),
                idx,
                length,
                model = that._modelForContainer(container);

                for (idx = 0, length = columns.length; idx < length; idx++) {
                    column = columns[idx];

                    if (model && (!model.editable || model.editable(column.field)) && !column.command && column.field) {
                        return idx;
                    }
                }
                return -1;
            },

            _toolbar: function () {
                var that = this,
                wrapper = that.wrapper,
                toolbar = that.options.toolbar,
                editable = that.options.editable,
                container;

                if (toolbar) {
                    container = that.wrapper.find(".k-grid-toolbar");

                    if (!container.length) {
                        if (!isFunction(toolbar)) {
                            toolbar = (typeof toolbar === STRING ? toolbar : that._toolbarTmpl(toolbar).replace(templateHashRegExp, "\\#"));
                            toolbar = proxy(kendo.template(toolbar), that);
                        }

                        container = $('<div class="k-header k-grid-toolbar" />')
                        .html(toolbar({}))
                        .prependTo(wrapper);

                        that.angular("compile", function () {
                            return { elements: container.get() };
                        });
                    }

                    if (editable && editable.create !== false) {
                        container.on(CLICK + NS, ".k-grid-add", function (e) { e.preventDefault(); that.addRow(); })
                        .on(CLICK + NS, ".k-grid-cancel-changes", function (e) { e.preventDefault(); that.cancelChanges(); })
                        .on(CLICK + NS, ".k-grid-save-changes", function (e) { e.preventDefault(); that.saveChanges(); });
                    }

                    container.on(CLICK + NS, ".k-grid-excel", function (e) {
                        e.preventDefault();

                        that.saveAsExcel();
                    });

                    container.on(CLICK + NS, ".k-grid-pdf", function (e) {
                        e.preventDefault();

                        that.saveAsPDF();
                    });
                }
            },

            _toolbarTmpl: function (commands) {
                var that = this,
                idx,
                length,
                html = "";

                if (isArray(commands)) {
                    for (idx = 0, length = commands.length; idx < length; idx++) {
                        html += that._createButton(commands[idx]);
                    }
                }
                return html;
            },

            _createButton: function (command) {
                var template = command.template || COMMANDBUTTONTMPL,
                commandName = typeof command === STRING ? command : command.name || command.text,
                className = defaultCommands[commandName] ? defaultCommands[commandName].className : "k-grid-" + (commandName || "").replace(/\s/g, ""),
                options = { className: className, text: commandName, imageClass: "", attr: "", iconClass: "" },
                messages = this.options.messages.commands,
                attributeClassMatch;

                if (!commandName && !(isPlainObject(command) && command.template)) {
                    throw new Error("Custom commands should have name specified");
                }

                if (isPlainObject(command)) {
                    if (command.className && inArray(options.className, command.className.split(" ")) < 0) {
                        command.className += " " + options.className;
                    } else if (command.className === undefined) {
                        command.className = options.className;
                    }

                    if (commandName === "edit" && isPlainObject(command.text)) {
                        command = extend(true, {}, command);
                        command.text = command.text.edit;
                    }

                    if (command.attr) {
                        if (isPlainObject(command.attr)) {
                            command.attr = stringifyAttributes(command.attr);
                        }

                        if (typeof command.attr === STRING) {
                            attributeClassMatch = command.attr.match(/class="(.+?)"/);

                            if (attributeClassMatch && inArray(attributeClassMatch[1], command.className.split(" ")) < 0) {
                                command.className += " " + attributeClassMatch[1];
                            }
                        }
                    }

                    options = extend(true, options, defaultCommands[commandName], { text: messages[commandName] }, command);
                } else {
                    options = extend(true, options, defaultCommands[commandName], { text: messages[commandName] });
                }

                return kendo.template(template)(options);
            },

            _hasFooters: function () {
                return !!this.footerTemplate ||
                !!this.groupFooterTemplate ||
                (this.footer && this.footer.length > 0) ||
                this.wrapper.find(".k-grid-footer").length > 0;
            },

            _groupable: function () {
                var that = this;

                if (that._groupableClickHandler) {
                    that.table.add(that.lockedTable).off(CLICK + NS, that._groupableClickHandler);
                } else {
                    that._groupableClickHandler = function (e) {
                        var element = $(this),
                    group = element.closest("tr");

                        if (element.hasClass('k-i-collapse')) {
                            that.collapseGroup(group);
                        } else {
                            that.expandGroup(group);
                        }
                        e.preventDefault();
                        e.stopPropagation();
                    };
                }

                if (that._isLocked()) {
                    that.lockedTable.on(CLICK + NS, ".k-grouping-row .k-i-collapse, .k-grouping-row .k-i-expand", that._groupableClickHandler);
                } else {
                    that.table.on(CLICK + NS, ".k-grouping-row .k-i-collapse, .k-grouping-row .k-i-expand", that._groupableClickHandler);
                }

                that._attachGroupable();
            },

            _attachGroupable: function () {
                var that = this,
                wrapper = that.wrapper,
                groupable = that.options.groupable,
                draggables = HEADERCELLS + "[" + kendo.attr("field") + "]",
                filter = that.content ? ".k-grid-header:first " + draggables : "table:first>.k-grid-header " + draggables;

                if (groupable && groupable.enabled !== false) {

                    if (!wrapper.has("div.k-grouping-header")[0]) {
                        $("<div>&nbsp;</div>").addClass("k-grouping-header").prependTo(wrapper);
                    }

                    if (that.groupable) {
                        that.groupable.destroy();
                    }

                    that.groupable = new Groupable(wrapper, extend({}, groupable, {
                        draggable: that._draggableInstance,
                        groupContainer: ">div.k-grouping-header",
                        dataSource: that.dataSource,
                        draggableElements: filter,
                        filter: filter,
                        allowDrag: that.options.reorderable
                    }));
                }
            },

            _continuousItems: function (filter, cell) {
                if (!this.lockedContent) {
                    return;
                }

                var that = this;

                var elements = that.table.add(that.lockedTable);

                var lockedItems = $(filter, elements[0]);
                var nonLockedItems = $(filter, elements[1]);
                var columns = cell ? lockedColumns(that.columns).length : 1;
                var nonLockedColumns = cell ? that.columns.length - columns : 1;
                var result = [];

                for (var idx = 0; idx < lockedItems.length; idx += columns) {
                    push.apply(result, lockedItems.slice(idx, idx + columns));
                    push.apply(result, nonLockedItems.splice(0, nonLockedColumns));
                }

                return result;
            },

            _selectable: function () {
                var that = this,
                multi,
                cell,
                notString = [],
                isLocked = that._isLocked(),
                selectable = that.options.selectable;

                if (selectable) {

                    if (that.selectable) {
                        that.selectable.destroy();
                    }

                    selectable = kendo.ui.Selectable.parseOptions(selectable);

                    multi = selectable.multiple;
                    cell = selectable.cell;

                    if (that._hasDetails()) {
                        notString[notString.length] = ".k-detail-row";
                    }
                    if (that.options.groupable || that._hasFooters()) {
                        notString[notString.length] = ".k-grouping-row,.k-group-footer";
                    }

                    notString = notString.join(",");

                    if (notString !== "") {
                        notString = ":not(" + notString + ")";
                    }

                    var elements = that.table;
                    if (isLocked) {
                        elements = elements.add(that.lockedTable);
                    }

                    var filter = ">" + (cell ? SELECTION_CELL_SELECTOR : "tbody>tr" + notString);
                    that.selectable = new kendo.ui.Selectable(elements, {
                        filter: filter,
                        aria: true,
                        multiple: multi,
                        change: function () {
                            that.trigger(CHANGE);
                        },
                        useAllItems: isLocked && multi && cell,
                        relatedTarget: function (items) {
                            if (cell || !isLocked) {
                                return;
                            }

                            var related;
                            var result = $();
                            for (var idx = 0, length = items.length; idx < length; idx++) {
                                related = that._relatedRow(items[idx]);

                                if (inArray(related[0], items) < 0) {
                                    result = result.add(related);
                                }
                            }

                            return result;
                        },
                        continuousItems: function () {
                            return that._continuousItems(filter, cell);
                        }
                    });

                    if (that.options.navigatable) {
                        elements.on("keydown" + NS, function (e) {
                            var current = that.current();
                            var target = e.target;
                            if (e.keyCode === keys.SPACEBAR && $.inArray(target, elements) > -1 &&
                            !current.is(".k-edit-cell,.k-header") &&
                            current.parent().is(":not(.k-grouping-row,.k-detail-row,.k-group-footer)")) {
                                e.preventDefault();
                                e.stopPropagation();
                                current = cell ? current : current.parent();

                                if (isLocked && !cell) {
                                    current = current.add(that._relatedRow(current));
                                }

                                if (multi) {
                                    if (!e.ctrlKey) {
                                        that.selectable.clear();
                                    } else {
                                        if (current.hasClass(SELECTED)) {
                                            current.removeClass(SELECTED);
                                            that.trigger(CHANGE);
                                            return;
                                        }
                                    }
                                } else {
                                    that.selectable.clear();
                                }

                                that.selectable.value(current);
                            }
                        });
                    }
                }
            },

            _relatedRow: function (row) {
                var lockedTable = this.lockedTable;
                row = $(row);

                if (!lockedTable) {
                    return row;
                }

                var table = row.closest(this.table.add(this.lockedTable));
                var index = table.find(">tbody>tr").index(row);

                table = table[0] === this.table[0] ? lockedTable : this.table;

                return table.find(">tbody>tr").eq(index);
            },

            clearSelection: function () {
                var that = this;
                that.selectable.clear();
                that.trigger(CHANGE);
            },

            select: function (items) {
                var that = this,
                selectable = that.selectable;

                items = $(items);
                if (items.length) {
                    if (!selectable.options.multiple) {
                        selectable.clear();
                        items = items.first();
                    }

                    if (that._isLocked()) {
                        items = items.add(items.map(function () {
                            return that._relatedRow(this);
                        }));
                    }

                    selectable.value(items);
                    return;
                }

                return selectable.value();
            },

            current: function (element) {
                var that = this,
                scrollable = that.options.scrollable,
                current = that._current,
                table = that.table.add(that.thead.parent());

                if (element !== undefined && element.length) {
                    if (!current || current[0] !== element[0]) {
                        if (current) {
                            current.removeClass(FOCUSED).removeAttr("id");
                            table.removeAttr("aria-activedescendant");
                        }

                        element.attr("id", that._cellId);
                        that._current = element.addClass(FOCUSED);

                        table.attr("aria-activedescendant", that._cellId);

                        if (element.length && scrollable) {
                            var content = element.closest("table").parent();
                            if (content.is(".k-grid-content")) {
                                that._scrollTo(element.parent()[0], that.content[0]);
                            } else if (content.is(".k-grid-content-locked")) {
                                that._scrollTo(that._relatedRow(element.parent())[0], that.content[0]);
                                that.lockedContent[0].scrollTop = that.content[0].scrollTop;
                            }

                            if (!content.is(".k-grid-content-locked,.k-grid-header-locked")) {
                                if (scrollable.virtual) {
                                    that._scrollTo(element[0], that.content.find(">.k-virtual-scrollable-wrap")[0]);
                                } else {
                                    that._scrollTo(element[0], that.content[0]);
                                }
                            }
                        }
                    }
                }

                return that._current;
            },

            _removeCurrent: function () {
                if (this._current) {
                    this._current.removeClass(FOCUSED);
                    this._current = null;
                }
            },

            _scrollTo: function (element, container) {
                var elementToLowercase = element.tagName.toLowerCase(),
                isHorizontal = elementToLowercase === "td" || elementToLowercase === "th",
                elementOffset = element[isHorizontal ? "offsetLeft" : "offsetTop"],
                elementOffsetDir = element[isHorizontal ? "offsetWidth" : "offsetHeight"],
                containerScroll = container[isHorizontal ? "scrollLeft" : "scrollTop"],
                containerOffsetDir = container[isHorizontal ? "clientWidth" : "clientHeight"],
                bottomDistance = elementOffset + elementOffsetDir,
                result = 0;

                if (containerScroll > elementOffset) {
                    result = elementOffset;
                } else if (bottomDistance > (containerScroll + containerOffsetDir)) {
                    if (elementOffsetDir <= containerOffsetDir) {
                        result = (bottomDistance - containerOffsetDir);
                    } else {
                        result = elementOffset;
                    }
                } else {
                    result = containerScroll;
                }
                container[isHorizontal ? "scrollLeft" : "scrollTop"] = result;
            },

            _navigatable: function () {
                var that = this,
                currentProxy = proxy(that.current, that),
                table = that.table.add(that.lockedTable),
                headerTable = that.thead.parent().add($(">table", that.lockedHeader)),
                isLocked = that._isLocked(),
                dataTable = table,
                isRtl = kendo.support.isRtl(that.element);

                if (!that.options.navigatable) {
                    return;
                }

                if (that.options.scrollable) {
                    dataTable = table.add(headerTable);
                    headerTable.attr(TABINDEX, -1);
                }

                dataTable.off("mousedown" + NS + " focus" + NS + " focusout" + NS + " keydown" + NS);

                headerTable.on("keydown" + NS, function (e) {
                    if (e.altKey && e.keyCode == keys.DOWN) {
                        currentProxy().find(".k-grid-filter, .k-header-column-menu").click();
                        e.stopImmediatePropagation();
                    }
                })
            .find("a.k-link").attr("tabIndex", -1);

                table
            .attr(TABINDEX, math.max(table.attr(TABINDEX) || 0, 0))
            .on("mousedown" + NS + " keydown" + NS, ".k-detail-cell", function (e) {
                if (e.target !== e.currentTarget) {
                    e.stopImmediatePropagation();
                }
            });

                dataTable
            .on((kendo.support.touch ? "touchstart" + NS : "mousedown" + NS), NAVROW + ">" + NAVCELL, proxy(tableClick, that))
            .on("focus" + NS, function () {
                if (kendo.support.touch) {
                    return;
                }

                var current = currentProxy();
                if (current && current.is(":visible")) {
                    current.addClass(FOCUSED);
                } else {
                    currentProxy($(this).find(FIRSTNAVITEM));
                }

                table.attr(TABINDEX, -1);
                headerTable.attr(TABINDEX, -1);
                $(this).attr(TABINDEX, 0);
            })
            .on("focusout" + NS, function () {
                var current = currentProxy();
                if (current) {
                    current.removeClass(FOCUSED);
                }
            })
            .on("keydown" + NS, function (e) {
                var key = e.keyCode,
                    handled = false,
                    canHandle = !e.isDefaultPrevented() && !$(e.target).is(":button,a,:input,a>.k-icon"),
                    pageable = that.options.pageable,
                    dataSource = that.dataSource,
                    isInCell = that._editMode() == "incell",
                    active,
                    currentIndex,
                    row,
                    index,
                    tableToFocus,
                    shiftKey = e.shiftKey,
                    relatedRow = proxy(that._relatedRow, that),
                    current = currentProxy();

                if (current && current.is("th")) {
                    canHandle = true;
                }

                if (canHandle && key == keys.UP) {
                    currentProxy(moveVertical(current, e.currentTarget, table, headerTable, true));
                    handled = true;
                } else if (canHandle && key == keys.DOWN) {
                    currentProxy(moveVertical(current, e.currentTarget, table, headerTable));
                    handled = true;
                } else if (canHandle && key == (isRtl ? keys.RIGHT : keys.LEFT)) {
                    currentProxy(moveLeft(current, e.currentTarget, table, headerTable, relatedRow));
                    handled = true;
                } else if (canHandle && key == (isRtl ? keys.LEFT : keys.RIGHT)) {
                    currentProxy(moveRight(current, e.currentTarget, table, headerTable, relatedRow));
                    handled = true;
                } else if (canHandle && pageable && keys.PAGEDOWN == key) {
                    dataSource.page(dataSource.page() + 1);
                    handled = true;
                } else if (canHandle && pageable && keys.PAGEUP == key) {
                    dataSource.page(dataSource.page() - 1);
                    handled = true;
                } else if (key == keys.ENTER || keys.F2 == key) {
                    current = current ? current : table.find(FIRSTNAVITEM);

                    if (!current.length) {
                        return;
                    }

                    if (!$(e.target).is("table") && !$.contains(current[0], e.target)) {
                        current = $(e.target).closest("[role=gridcell]");
                    }

                    if (current.is("th")) {
                        current.find(".k-link").click();
                        handled = true;
                    } else if (current.parent().is(".k-master-row,.k-grouping-row")) {
                        current.parent().find(".k-icon:first").click();
                        handled = true;
                    } else {
                        var focusable = current.find(":kendoFocusable:first");
                        if (!current.hasClass("k-edit-cell") && focusable[0] && current.hasClass("k-state-focused")) {
                            focusable.focus();
                            handled = true;
                        } else if (that.options.editable && !$(e.target).is(":button,.k-button,textarea")) {
                            var container = $(e.target).closest("[role=gridcell]");
                            if (!container[0]) {
                                container = current;
                            }

                            that._handleEditing(container, false, isInCell ? e.currentTarget : table[0]);
                            handled = true;
                        }
                    }
                } else if (keys.ESC == key) {
                    active = activeElement();
                    if (current && $.contains(current[0], active) && !current.hasClass("k-edit-cell") && !current.parent().hasClass("k-grid-edit-row")) {
                        focusTable(e.currentTarget, true);
                        handled = true;
                    } else if (that._editContainer && (!current || that._editContainer.has(current[0]) || current[0] === that._editContainer[0])) {
                        if (isInCell) {
                            that.closeCell(true);
                        } else {
                            currentIndex = $(current).parent().index();
                            if (active) {
                                active.blur();
                            }
                            that.cancelRow();
                            if (currentIndex >= 0) {
                                that.current(table.find(">tbody>tr").eq(currentIndex).children().filter(NAVCELL).first());
                            }
                        }

                        if (browser.msie && browser.version < 9) {
                            document.body.focus();
                        }
                        focusTable(isInCell ? e.currentTarget : table[0], true);
                        handled = true;
                    }
                } else if (keys.TAB == key) {
                    var cell;

                    current = $(current);
                    if (that.options.editable && isInCell) {
                        cell = $(activeElement()).closest(".k-edit-cell");

                        if (cell[0] && cell[0] !== current[0]) {
                            current = cell;
                        }
                    }

                    cell = tabNext(current, e.currentTarget, table, relatedRow, shiftKey);

                    if (!current.is("th") && cell.length && that.options.editable && isInCell) {
                        that._handleEditing(current, cell, cell.closest(table));
                        handled = true;
                    }
                }

                if (handled) {
                    //prevent browser scrolling
                    e.preventDefault();
                    //required in hierarchy
                    e.stopPropagation();
                }
            });
            },

            _handleEditing: function (current, next, table) {
                var that = this,
                active = $(activeElement()),
                mode = that._editMode(),
                isIE = browser.msie,
                oldIE = isIE && browser.version < 9,
                editContainer = that._editContainer,
                focusable,
                isEdited;

                table = $(table);
                if (mode == "incell") {
                    isEdited = current.hasClass("k-edit-cell");
                } else {
                    isEdited = current.parent().hasClass("k-grid-edit-row");
                }

                if (that.editable) {
                    if ($.contains(editContainer[0], active[0])) {
                        if (browser.opera || oldIE) {
                            active.change().triggerHandler("blur");
                        } else {
                            active.blur();
                            if (isIE) {
                                //IE10 with jQuery 1.9.x does not trigger blur handler
                                //numeric textbox does trigger change
                                active.blur();
                            }
                        }
                    }

                    if (!that.editable) {
                        focusTable(table);
                        return;
                    }

                    if (that.editable.end()) {
                        if (mode == "incell") {
                            that.closeCell();
                        } else {
                            that.saveRow();
                            isEdited = true;
                        }
                    } else {
                        if (mode == "incell") {
                            that.current(editContainer);
                        } else {
                            that.current(editContainer.children().filter(DATA_CELL).first());
                        }
                        focusable = editContainer.find(":kendoFocusable:first")[0];
                        if (focusable) {
                            focusable.focus();
                        }
                        return;
                    }
                }

                if (next) {
                    that.current(next);
                }

                if (oldIE) {
                    document.body.focus();
                }
                focusTable(table, true);
                if ((!isEdited && !next) || next) {
                    if (mode == "incell") {
                        that.editCell(that.current());
                    } else {
                        that.editRow(that.current().parent());
                    }
                }
            },

            _wrapper: function () {
                var that = this,
                table = that.table,
                height = that.options.height,
                wrapper = that.element;

                if (!wrapper.is("div")) {
                    wrapper = wrapper.wrap("<div/>").parent();
                }

                that.wrapper = wrapper.addClass("k-grid k-widget");

                if (height) {
                    that.wrapper.css(HEIGHT, height);
                    table.css(HEIGHT, "auto");
                }

                that._initMobile();
            },

            _initMobile: function () {
                var options = this.options;
                var that = this;

                this._isMobile = (options.mobile === true && kendo.support.mobileOS) ||
                                options.mobile === "phone" ||
                                options.mobile === "tablet";

                if (this._isMobile) {
                    var html = this.wrapper.addClass("k-grid-mobile").wrap(
                        '<div data-' + kendo.ns + 'role="view" ' +
                        'data-' + kendo.ns + 'init-widgets="false"></div>'
                    )
                    .parent();

                    this.pane = kendo.mobile.ui.Pane.wrap(html);
                    this.view = this.pane.view();
                    this._actionSheetPopupOptions = $(document.documentElement).hasClass("km-root") ? { modal: false} : {
                        align: "bottom center",
                        position: "bottom center",
                        effect: "slideIn:up"
                    };

                    if (options.height) {
                        this.pane.element.parent().css(HEIGHT, options.height);
                    }

                    this._editAnimation = "slide";

                    this.view.bind("show", function () {
                        if (that._isLocked()) {
                            that._updateTablesWidth();
                            that._applyLockedContainersWidth();
                            that._syncLockedContentHeight();
                            that._syncLockedHeaderHeight();
                            that._syncLockedFooterHeight();
                        }
                    });
                }
            },

            _tbody: function () {
                var that = this,
                table = that.table,
                tbody;

                tbody = table.find(">tbody");

                if (!tbody.length) {
                    tbody = $("<tbody/>").appendTo(table);
                }

                that.tbody = tbody.attr("role", "rowgroup");
            },

            _scrollable: function () {
                var that = this,
                header,
                table,
                options = that.options,
                scrollable = options.scrollable,
                hasVirtualScroll = scrollable !== true && scrollable.virtual && !that.virtualScrollable,
                scrollbar = !kendo.support.kineticScrollNeeded || hasVirtualScroll ? kendo.support.scrollbar() : 0;

                if (scrollable) {
                    header = that.wrapper.children(".k-grid-header");

                    if (!header[0]) {
                        header = $('<div class="k-grid-header" />').insertBefore(that.table);
                    }

                    // workaround for IE issue where scroll is not raised if container is same width as the scrollbar
                    header.css((isRtl ? "padding-left" : "padding-right"), scrollable.virtual ? scrollbar + 1 : scrollbar);
                    table = $('<table role="grid" />');
                    if (isIE7) {
                        table.attr("cellspacing", 0);
                    }
                    table.append(that.thead);
                    header.empty().append($('<div class="k-grid-header-wrap" />').append(table));


                    that.content = that.table.parent();

                    if (that.content.is(".k-virtual-scrollable-wrap, .km-scroll-container")) {
                        that.content = that.content.parent();
                    }

                    if (!that.content.is(".k-grid-content, .k-virtual-scrollable-wrap")) {
                        that.content = that.table.wrap('<div class="k-grid-content" />').parent();
                    }
                    if (hasVirtualScroll) {
                        that.virtualScrollable = new VirtualScrollable(that.content, {
                            dataSource: that.dataSource,
                            itemHeight: function () { return that._averageRowHeight(); }
                        });
                    }

                    that.scrollables = header.children(".k-grid-header-wrap");

                    // the footer may exists if rendered from the server
                    var footer = that.wrapper.find(".k-grid-footer");

                    if (footer.length) {
                        that.scrollables = that.scrollables.add(footer.children(".k-grid-footer-wrap"));
                    }

                    if (scrollable.virtual) {
                        that.content.find(">.k-virtual-scrollable-wrap").unbind("scroll" + NS).bind("scroll" + NS, function () {
                            that.scrollables.scrollLeft(this.scrollLeft);
                            if (that.lockedContent) {
                                that.lockedContent[0].scrollTop = this.scrollTop;
                            }
                        });
                    } else {
                        that.content.unbind("scroll" + NS).bind("scroll" + NS, function () {
                            that.scrollables.scrollLeft(this.scrollLeft);
                            if (that.lockedContent) {
                                that.lockedContent[0].scrollTop = this.scrollTop;
                            }
                        });

                        var touchScroller = that.content.data("kendoTouchScroller");
                        if (touchScroller) {
                            touchScroller.destroy();
                        }

                        touchScroller = kendo.touchScroller(that.content);
                        if (touchScroller && touchScroller.movable) {
                            that.touchScroller = touchScroller;
                            touchScroller.movable.bind("change", function (e) {
                                that.scrollables.scrollLeft(-e.sender.x);
                                if (that.lockedContent) {
                                    that.lockedContent.scrollTop(-e.sender.y);
                                }
                            });

                            that.one(DATABOUND, function (e) {
                                e.sender.wrapper.addClass("k-grid-backface");
                            });
                        }
                    }
                }
            },

            _setContentWidth: function () {
                var that = this,
                hiddenDivClass = 'k-grid-content-expander',
                hiddenDiv = '<div class="' + hiddenDivClass + '"></div>',
                resizable = that.resizable,
                expander;

                if (that.options.scrollable && that.wrapper.is(":visible")) {
                    expander = that.table.parent().children('.' + hiddenDivClass);
                    that._setContentWidthHandler = proxy(that._setContentWidth, that);
                    if (!that.dataSource || !that.dataSource.view().length) {
                        if (!expander[0]) {
                            expander = $(hiddenDiv).appendTo(that.table.parent());
                            if (resizable) {
                                resizable.bind("resize", that._setContentWidthHandler);
                            }
                        }
                        if (that.thead) {
                            expander.width(that.thead.width());
                        }
                    } else if (expander[0]) {
                        expander.remove();
                        if (resizable) {
                            resizable.unbind("resize", that._setContentWidthHandler);
                        }
                    }

                    that._applyLockedContainersWidth();
                }
            },

            _applyLockedContainersWidth: function () {
                if (this.options.scrollable && this.lockedHeader) {
                    var headerTable = this.thead.parent(),
                    headerWrap = headerTable.parent(),
                    contentWidth = this.wrapper[0].clientWidth,
                    groups = this._groups(),
                    scrollbar = kendo.support.scrollbar(),
                    cols = this.lockedHeader.find(">table>colgroup>col:not(.k-group-col, .k-hierarchy-col)"),
                    nonLockedCols = headerTable.find(">colgroup>col:not(.k-group-col, .k-hierarchy-col)"),
                    width = columnsWidth(cols),
                    nonLockedColsWidth = columnsWidth(nonLockedCols),
                    footerWrap;

                    if (groups > 0) {
                        width += this.lockedHeader.find(".k-group-cell:first").outerWidth() * groups;
                    }

                    if (width >= contentWidth) {
                        width = contentWidth - 3 * scrollbar;
                    }

                    this.lockedHeader
                    .add(this.lockedContent)
                    .width(width);

                    headerWrap[0].style.width = headerWrap.parent().width() - width - 2 + "px";

                    headerTable.add(this.table).width(nonLockedColsWidth);

                    if (this.virtualScrollable) {
                        contentWidth -= scrollbar;
                    }

                    this.content[0].style.width = contentWidth - width - 2 + "px";

                    if (this.lockedFooter && this.lockedFooter.length) {
                        this.lockedFooter.width(width);
                        footerWrap = this.footer.find(".k-grid-footer-wrap");
                        footerWrap[0].style.width = headerWrap[0].clientWidth + "px";
                        footerWrap.children().first().width(nonLockedColsWidth);
                    }
                }
            },

            _setContentHeight: function () {
                var that = this,
                options = that.options,
                height = that.wrapper.innerHeight(),
                header = that.wrapper.children(".k-grid-header"),
                scrollbar = kendo.support.scrollbar();

                if (options.scrollable && that.wrapper.is(":visible")) {

                    height -= header.outerHeight();

                    if (that.pager) {
                        height -= that.pager.element.outerHeight();
                    }

                    if (options.groupable) {
                        height -= that.wrapper.children(".k-grouping-header").outerHeight();
                    }

                    if (options.toolbar) {
                        height -= that.wrapper.children(".k-grid-toolbar").outerHeight();
                    }

                    if (that.footerTemplate) {
                        height -= that.wrapper.children(".k-grid-footer").outerHeight();
                    }

                    var isGridHeightSet = function (el) {
                        var initialHeight, newHeight;
                        if (el[0].style.height) {
                            return true;
                        } else {
                            initialHeight = el.height();
                        }

                        el.height("auto");
                        newHeight = el.height();

                        if (initialHeight != newHeight) {
                            el.height("");
                            return true;
                        }
                        el.height("");
                        return false;
                    };

                    if (isGridHeightSet(that.wrapper)) { // set content height only if needed
                        if (height > scrollbar * 2) { // do not set height if proper scrollbar cannot be displayed
                            if (that.lockedContent) {
                                scrollbar = that.table[0].offsetWidth > that.table.parent()[0].clientWidth ? scrollbar : 0;
                                that.lockedContent.height(height - scrollbar);
                            }

                            that.content.height(height);
                        } else {
                            that.content.height(scrollbar * 2 + 1);
                        }
                    }
                }
            },

            _averageRowHeight: function () {
                var that = this,
                itemsCount = that._items(that.tbody).length,
                rowHeight = that._rowHeight;

                if (itemsCount === 0) {
                    return rowHeight;
                }

                if (!that._rowHeight) {
                    that._rowHeight = rowHeight = that.table.outerHeight() / itemsCount;
                    that._sum = rowHeight;
                    that._measures = 1;
                }

                var currentRowHeight = that.table.outerHeight() / itemsCount;

                if (rowHeight !== currentRowHeight) {
                    that._measures++;
                    that._sum += currentRowHeight;
                    that._rowHeight = that._sum / that._measures;
                }
                return rowHeight;
            },

            _dataSource: function () {
                var that = this,
                options = that.options,
                pageable,
                dataSource = options.dataSource;

                dataSource = isArray(dataSource) ? { data: dataSource} : dataSource;

                if (isPlainObject(dataSource)) {
                    extend(dataSource, { table: that.table, fields: that.columns });

                    pageable = options.pageable;

                    if (isPlainObject(pageable) && pageable.pageSize !== undefined) {
                        dataSource.pageSize = pageable.pageSize;
                    }
                }

                if (that.dataSource && that._refreshHandler) {
                    that.dataSource.unbind(CHANGE, that._refreshHandler)
                                .unbind(PROGRESS, that._progressHandler)
                                .unbind(ERROR, that._errorHandler);
                } else {
                    that._refreshHandler = proxy(that.refresh, that);
                    that._progressHandler = proxy(that._requestStart, that);
                    that._errorHandler = proxy(that._error, that);
                }

                that.dataSource = DataSource.create(dataSource)
                                .bind(CHANGE, that._refreshHandler)
                                .bind(PROGRESS, that._progressHandler)
                                .bind(ERROR, that._errorHandler);
            },

            _error: function () {
                this._progress(false);
            },

            _requestStart: function () {
                this._progress(true);
            },

            _modelChange: function (e) {
                var that = this,
                tbody = that.tbody,
                model = e.model,
                row = that.tbody.find("tr[" + kendo.attr("uid") + "=" + model.uid + "]"),
                relatedRow,
                cell,
                column,
                isAlt = row.hasClass("k-alt"),
                tmp,
                idx = that._items(tbody).index(row),
                isLocked = that.lockedContent,
                selectable,
                selectableRow,
                childCells,
                originalCells,
                length;

                if (isLocked) {
                    relatedRow = that._relatedRow(row);
                }

                if (row.add(relatedRow).children(".k-edit-cell").length && !that.options.rowTemplate) {
                    row.add(relatedRow).children(":not(.k-group-cell,.k-hierarchy-cell)").each(function () {
                        cell = $(this);
                        column = leafColumns(that.columns)[that.cellIndex(cell)];

                        if (column.field === e.field) {
                            if (!cell.hasClass("k-edit-cell")) {
                                that._displayCell(cell, column, model);
                                $('<span class="k-dirty"/>').prependTo(cell);
                            } else {
                                cell.addClass("k-dirty-cell");
                            }
                        }
                    });

                } else if (!row.hasClass("k-grid-edit-row")) {

                    selectableRow = $().add(row);

                    if (isLocked) {
                        tmp = (isAlt ? that.lockedAltRowTemplate : that.lockedRowTemplate)(model);

                        selectableRow = selectableRow.add(relatedRow);

                        relatedRow.replaceWith(tmp);
                    }

                    tmp = (isAlt ? that.altRowTemplate : that.rowTemplate)(model);

                    row.replaceWith(tmp);

                    tmp = that._items(tbody).eq(idx);

                    if (isLocked) {
                        row = row.add(relatedRow);

                        relatedRow = that._relatedRow(tmp)[0];
                        adjustRowHeight(tmp[0], relatedRow);

                        tmp = tmp.add(relatedRow);
                    }

                    selectable = that.options.selectable;
                    if (selectable && row.hasClass("k-state-selected")) {
                        that.select(tmp);
                    }

                    originalCells = selectableRow.children(":not(.k-group-cell,.k-hierarchy-cell)");
                    childCells = tmp.children(":not(.k-group-cell,.k-hierarchy-cell)");

                    for (idx = 0, length = that.columns.length; idx < length; idx++) {
                        column = that.columns[idx];

                        cell = childCells.eq(idx);
                        if (selectable && originalCells.eq(idx).hasClass("k-state-selected")) {
                            cell.addClass("k-state-selected");
                        }

                        if (column.field === e.field) {
                            $('<span class="k-dirty"/>').prependTo(cell);
                        }
                    }

                    that.trigger("itemChange", { item: tmp, data: model, ns: ui });
                }
            },

            _pageable: function () {
                var that = this,
                wrapper,
                pageable = that.options.pageable;

                if (pageable) {
                    wrapper = that.wrapper.children("div.k-grid-pager");

                    if (!wrapper.length) {
                        wrapper = $('<div class="k-pager-wrap k-grid-pager"/>').appendTo(that.wrapper);
                    }

                    if (that.pager) {
                        that.pager.destroy();
                    }

                    if (typeof pageable === "object" && pageable instanceof kendo.ui.Pager) {
                        that.pager = pageable;
                    } else {
                        that.pager = new kendo.ui.Pager(wrapper, extend({}, pageable, { dataSource: that.dataSource }));
                    }
                }
            },

            _footer: function () {
                var that = this,
                aggregates = that.dataSource.aggregates(),
                html = "",
                footerTemplate = that.footerTemplate,
                options = that.options,
                footerWrap,
                footer = that.footer || that.wrapper.find(".k-grid-footer");

                if (footerTemplate) {
                    aggregates = !isEmptyObject(aggregates) ? aggregates : buildEmptyAggregatesObject(that.dataSource.aggregate());

                    html = $(that._wrapFooter(footerTemplate(aggregates)));

                    if (footer.length) {
                        var tmp = html;

                        that.angular("cleanup", function () {
                            return { elements: footer.get() };
                        });

                        footer.replaceWith(tmp);
                        footer = that.footer = tmp;
                    } else {
                        if (options.scrollable) {
                            footer = that.footer = options.pageable ? html.insertBefore(that.wrapper.children("div.k-grid-pager")) : html.appendTo(that.wrapper);
                        } else {
                            footer = that.footer = html.insertBefore(that.tbody);
                        }
                    }

                    that.angular("compile", function () {
                        return {
                            elements: footer.find("td").get(),
                            data: map(that.columns, function (col, i) {
                                return {
                                    column: col,
                                    aggregate: aggregates[col.field]
                                };
                            })
                        };
                    });

                } else if (footer && !that.footer) {
                    that.footer = footer;
                }

                if (footer.length) {
                    if (options.scrollable) {
                        footerWrap = footer.attr("tabindex", -1).children(".k-grid-footer-wrap");
                        that.scrollables = that.scrollables
                        .filter(function () { return !$(this).is(".k-grid-footer-wrap"); })
                        .add(footerWrap);
                    }

                    if (that._footerWidth) {
                        footer.find("table").css('width', that._footerWidth);
                    }

                    if (footerWrap) {
                        var offset = that.content.scrollLeft();

                        var hasVirtualScroll = options.scrollable !== true && options.scrollable.virtual && !that.virtualScrollable;
                        if (hasVirtualScroll) {
                            offset = that.wrapper.find('.k-virtual-scrollable-wrap').scrollLeft();
                        }
                        footerWrap.scrollLeft(offset);
                    }
                }

                if (that.lockedContent) {
                    that._appendLockedColumnFooter();
                    that._applyLockedContainersWidth();
                    that._syncLockedFooterHeight();
                }
            },

            _wrapFooter: function (footerRow) {
                var that = this,
                html = "",
                scrollbar = !kendo.support.mobileOS ? kendo.support.scrollbar() : 0;

                if (that.options.scrollable) {
                    html = $('<div class="k-grid-footer"><div class="k-grid-footer-wrap"><table' + (isIE7 ? ' cellspacing="0"' : '') + '><tbody>' + footerRow + '</tbody></table></div></div>');
                    that._appendCols(html.find("table"));
                    html.css((isRtl ? "padding-left" : "padding-right"), scrollbar); // Update inner fix.

                    return html;
                }

                return '<tfoot class="k-grid-footer">' + footerRow + '</tfoot>';
            },

            _columnMenu: function () {
                var that = this,
                menu,
                columns = leafColumns(that.columns),
                column,
                options = that.options,
                columnMenu = options.columnMenu,
                menuOptions,
                sortable,
                filterable,
                cells,
                hasMultiColumnHeaders = grep(that.columns, function (item) {
                    return item.columns !== undefined;
                }).length > 0,
                isMobile = this._isMobile,
                initCallback = function (e) {
                    that.trigger(COLUMNMENUINIT, { field: e.field, container: e.container });
                },
                closeCallback = function (element) {
                    focusTable(element.closest("table"), true);
                },
                $angular = options.$angular;

                if (columnMenu) {
                    if (typeof columnMenu == "boolean") {
                        columnMenu = {};
                    }

                    cells = leafDataCells(that.thead);

                    for (var idx = 0, length = cells.length; idx < length; idx++) {
                        column = columns[idx];
                        var cell = cells.eq(idx);

                        if (!column.command && (column.field || cell.attr("data-" + kendo.ns + "field"))) {
                            menu = cell.data("kendoColumnMenu");
                            if (menu) {
                                menu.destroy();
                            }

                            sortable = column.sortable !== false && columnMenu.sortable !== false && options.sortable !== false ? extend({}, options.sortable, {
                                compare: (column.sortable || {}).compare
                            }) : false;

                            filterable = options.filterable && column.filterable !== false && columnMenu.filterable !== false ? extend({ pane: that.pane }, column.filterable, options.filterable) : false;
                            menuOptions = {
                                dataSource: that.dataSource,
                                values: column.values,
                                columns: columnMenu.columns,
                                sortable: sortable,
                                filterable: filterable,
                                messages: columnMenu.messages,
                                owner: that,
                                closeCallback: closeCallback,
                                init: initCallback,
                                pane: that.pane,
                                filter: isMobile ? ":not(.k-column-active)" : "",
                                lockedColumns: !hasMultiColumnHeaders && column.lockable !== false && lockedColumns(columns).length > 0
                            };

                            if ($angular) {
                                menuOptions.$angular = $angular;
                            }

                            cell.kendoColumnMenu(menuOptions);
                        }
                    }
                }
            },

            _headerCells: function () {
                return this.thead.find("th").filter(function () {
                    var th = $(this);
                    return !th.hasClass("k-group-cell") && !th.hasClass("k-hierarchy-cell");
                });
            },

            _filterable: function () {
                var that = this,
                columns = leafColumns(that.columns),
                filterMenu,
                cells,
                cell,
                filterInit = function (e) {
                    that.trigger(FILTERMENUINIT, { field: e.field, container: e.container });
                },
                closeCallback = function (element) {
                    focusTable(element.closest("table"), true);
                },
                filterable = that.options.filterable;
                if (filterable && typeof filterable.mode == STRING && filterable.mode.indexOf("menu") == -1) {
                    filterable = false;
                }

                if (filterable && !that.options.columnMenu) {
                    cells = leafDataCells(that.thead); //that._headerCells();

                    for (var idx = 0, length = cells.length; idx < length; idx++) {
                        cell = cells.eq(idx);

                        if (columns[idx].filterable !== false && !columns[idx].command && (columns[idx].field || cell.attr("data-" + kendo.ns + "field"))) {
                            filterMenu = cell.data("kendoFilterMenu");

                            if (filterMenu) {
                                filterMenu.destroy();
                            }

                            var columnFilterable = columns[idx].filterable;

                            var options = extend({},
                            filterable,
                            columnFilterable,
                            {
                                dataSource: that.dataSource,
                                values: columns[idx].values,
                                closeCallback: closeCallback,
                                init: filterInit,
                                pane: that.pane
                            }
                        );

                            if (columnFilterable && columnFilterable.messages) {
                                options.messages = extend(true, {}, filterable.messages, columnFilterable.messages);
                            }

                            cell.kendoFilterMenu(options);
                        }
                    }
                }
            },

            _filterRow: function () {
                var that = this;
                if (!that._hasFilterRow()) {
                    return;
                }

                var columns = leafColumns(that.columns),
                filterable = that.options.filterable,
                rowheader = that.thead.find(".k-filter-row");

                this._updateHeader(this.dataSource.group().length);

                for (var i = 0; i < columns.length; i++) {
                    var suggestDataSource,
                    col = columns[i],
                    operators = that.options.filterable.operators,
                    customDataSource = false,
                    th = $("<th/>"),
                    field = col.field;

                    if (col.hidden) {
                        th.hide();
                    }
                    rowheader.append(th);
                    if (field && col.filterable !== false) {
                        var cellOptions = col.filterable && col.filterable.cell || {};

                        suggestDataSource = that.options.dataSource;
                        if (suggestDataSource instanceof DataSource) {
                            suggestDataSource = that.options.dataSource.options;
                        }

                        var messages = extend(true, {}, filterable.messages);
                        if (col.filterable) {
                            extend(true, messages, col.filterable.messages);
                        }

                        if (cellOptions.enabled === false) {
                            th.html("&nbsp;");
                            continue;
                        }
                        if (cellOptions.dataSource) {
                            suggestDataSource = cellOptions.dataSource;
                            customDataSource = true;
                        }
                        if (col.filterable && col.filterable.operators) {
                            operators = col.filterable.operators;
                        }

                        $("<span/>").attr(kendo.attr("field"), field)
                        .kendoFilterCell({
                            dataSource: that.dataSource,
                            suggestDataSource: suggestDataSource,
                            customDataSource: customDataSource,
                            field: field,
                            messages: messages,
                            values: col.values,
                            template: cellOptions.template,
                            delay: cellOptions.delay,
                            inputWidth: cellOptions.inputWidth,
                            suggestionOperator: cellOptions.suggestionOperator,
                            minLength: cellOptions.minLength,
                            dataTextField: cellOptions.dataTextField,
                            operator: cellOptions.operator,
                            operators: operators,
                            showOperators: cellOptions.showOperators
                        }).appendTo(th);
                    } else {
                        th.html("&nbsp;");
                    }
                }
            },

            _sortable: function () {
                var that = this,
                columns = leafColumns(that.columns),
                column,
                sorterInstance,
                cell,
                sortable = that.options.sortable;

                if (sortable) {
                    var cells = leafDataCells(that.thead);

                    for (var idx = 0, length = cells.length; idx < length; idx++) {
                        column = columns[idx];

                        if (column.sortable !== false && !column.command && column.field) {
                            cell = cells.eq(idx);

                            sorterInstance = cell.data("kendoColumnSorter");

                            if (sorterInstance) {
                                sorterInstance.destroy();
                            }

                            cell.attr("data-" + kendo.ns + "field", column.field)
                            .kendoColumnSorter(
                                extend({}, sortable, column.sortable, {
                                    dataSource: that.dataSource,
                                    aria: true,
                                    filter: ":not(.k-column-active)"
                                })
                            );
                        }
                    }
                    cells = null;
                }
            },

            _columns: function (columns) {
                var that = this,
                table = that.table,
                encoded,
                cols = table.find("col"),
                lockedCols,
                dataSource = that.options.dataSource;

                // using HTML5 data attributes as a configuration option e.g. <th data-field="foo">Foo</foo>
                columns = columns.length ? columns : map(table.find("th"), function (th, idx) {
                    th = $(th);
                    var sortable = th.attr(kendo.attr("sortable")),
                    filterable = th.attr(kendo.attr("filterable")),
                    type = th.attr(kendo.attr("type")),
                    groupable = th.attr(kendo.attr("groupable")),
                    field = th.attr(kendo.attr("field")),
                    menu = th.attr(kendo.attr("menu"));

                    if (!field) {
                        field = th.text().replace(/\s|[^A-z0-9]/g, "");
                    }

                    return {
                        field: field,
                        type: type,
                        sortable: sortable !== "false",
                        filterable: filterable !== "false",
                        groupable: groupable !== "false",
                        menu: menu,
                        template: th.attr(kendo.attr("template")),
                        width: cols.eq(idx).css("width")
                    };
                });

                encoded = !(that.table.find("tbody tr").length > 0 && (!dataSource || !dataSource.transport));

                if (that.options.scrollable) {
                    var initialColumns = columns;
                    lockedCols = lockedColumns(columns);
                    columns = nonLockedColumns(columns);

                    if (lockedCols.length > 0 && columns.length === 0) {
                        throw new Error("There should be at least one non locked columns");
                    }

                    normalizeHeaderCells(that.element.find("tr:has(th):first").find("th:not(.k-group-cell)"), initialColumns);
                    columns = lockedCols.concat(columns);
                }

                that.columns = normalizeColumns(columns, encoded);
            },

            _groups: function () {
                var group = this.dataSource.group();

                return group ? group.length : 0;
            },

            _tmpl: function (rowTemplate, columns, alt, skipGroupCells) {
                var that = this,
                settings = extend({}, kendo.Template, that.options.templateSettings),
                idx,
                length = columns.length,
                template,
                state = { storage: {}, count: 0 },
                column,
                type,
                hasDetails = that._hasDetails(),
                className = [],
                groups = that._groups();

                if (!rowTemplate) {
                    rowTemplate = "<tr";

                    if (alt) {
                        className.push("k-alt");
                    }

                    if (hasDetails) {
                        className.push("k-master-row");
                    }

                    if (className.length) {
                        rowTemplate += ' class="' + className.join(" ") + '"';
                    }

                    if (length) { // data item is an object
                        rowTemplate += ' ' + kendo.attr("uid") + '="#=' + kendo.expr("uid", settings.paramName) + '#"';
                    }

                    rowTemplate += " role='row'>";

                    if (groups > 0 && !skipGroupCells) {
                        rowTemplate += groupCells(groups);
                    }

                    if (hasDetails) {
                        rowTemplate += '<td class="k-hierarchy-cell"><a class="k-icon k-plus" href="\\#" tabindex="-1"></a></td>';
                    }

                    for (idx = 0; idx < length; idx++) {
                        column = columns[idx];
                        template = column.template;
                        type = typeof template;

                        rowTemplate += "<td" + stringifyAttributes(column.attributes) + " role='gridcell'>";
                        rowTemplate += that._cellTmpl(column, state);

                        rowTemplate += "</td>";
                    }

                    rowTemplate += "</tr>";
                }

                rowTemplate = kendo.template(rowTemplate, settings);

                if (state.count > 0) {
                    return proxy(rowTemplate, state.storage);
                }

                return rowTemplate;
            },

            _headerCellText: function (column) {
                var that = this,
                settings = extend({}, kendo.Template, that.options.templateSettings),
                template = column.headerTemplate,
                type = typeof (template),
                text = column.title || column.field || "";

                if (type === FUNCTION) {
                    text = kendo.template(template, settings)({});
                } else if (type === STRING) {
                    text = template;
                }
                return text;
            },

            _cellTmpl: function (column, state) {
                var that = this,
                settings = extend({}, kendo.Template, that.options.templateSettings),
                template = column.template,
                paramName = settings.paramName,
                field = column.field,
                html = "",
                idx,
                length,
                format = column.format,
                type = typeof template,
                columnValues = column.values;

                if (column.command) {
                    if (isArray(column.command)) {
                        for (idx = 0, length = column.command.length; idx < length; idx++) {
                            html += that._createButton(column.command[idx]);
                        }
                        return html.replace(templateHashRegExp, "\\#");
                    }
                    return that._createButton(column.command).replace(templateHashRegExp, "\\#");
                }
                if (type === FUNCTION) {
                    state.storage["tmpl" + state.count] = template;
                    html += "#=this.tmpl" + state.count + "(" + paramName + ")#";
                    state.count++;
                } else if (type === STRING) {
                    html += template;
                } else if (columnValues && columnValues.length && isPlainObject(columnValues[0]) && "value" in columnValues[0] && field) {
                    html += "#var v =" + kendo.stringify(convertToObject(columnValues)).replace(templateHashRegExp, "\\#") + "#";
                    html += "#var f = v[";

                    if (!settings.useWithBlock) {
                        html += paramName + ".";
                    }

                    html += field + "]#";
                    html += "${f != null ? f : ''}";
                } else {
                    html += column.encoded ? "#:" : "#=";

                    if (format) {
                        html += 'kendo.format(\"' + format.replace(formatRegExp, "\\$1") + '\",';
                    }

                    if (field) {
                        field = kendo.expr(field, paramName);
                        html += field + "==null?'':" + field;
                    } else {
                        html += "''";
                    }

                    if (format) {
                        html += ")";
                    }

                    html += "#";
                }
                return html;
            },

            _templates: function () {
                var that = this,
                options = that.options,
                dataSource = that.dataSource,
                groups = dataSource.group(),
                footer = that.footer || that.wrapper.find(".k-grid-footer"),
                aggregates = dataSource.aggregate(),
                columnLeafs = leafColumns(that.columns),
                columnsLocked = leafColumns(lockedColumns(that.columns)),
                columns = options.scrollable ? leafColumns(nonLockedColumns(that.columns)) : columnLeafs;

                if (options.scrollable && columnsLocked.length) {
                    if (options.rowTemplate || options.altRowTemplate) {
                        throw new Error("Having both row template and locked columns is not supported");
                    }

                    that.rowTemplate = that._tmpl(options.rowTemplate, columns, false, true);
                    that.altRowTemplate = that._tmpl(options.altRowTemplate || options.rowTemplate, columns, true, true);

                    that.lockedRowTemplate = that._tmpl(options.rowTemplate, columnsLocked);
                    that.lockedAltRowTemplate = that._tmpl(options.altRowTemplate || options.rowTemplate, columnsLocked, true);
                } else {
                    that.rowTemplate = that._tmpl(options.rowTemplate, columns);
                    that.altRowTemplate = that._tmpl(options.altRowTemplate || options.rowTemplate, columns, true);
                }

                if (that._hasDetails()) {
                    that.detailTemplate = that._detailTmpl(options.detailTemplate || "");
                }

                if ((that._group && !isEmptyObject(aggregates)) || (!isEmptyObject(aggregates) && !footer.length) ||
                grep(columnLeafs, function (column) { return column.footerTemplate; }).length) {

                    that.footerTemplate = that._footerTmpl(columnLeafs, aggregates, "footerTemplate", "k-footer-template");
                }

                if (groups && grep(columnLeafs, function (column) { return column.groupFooterTemplate; }).length) {
                    aggregates = $.map(groups, function (g) { return g.aggregates; });

                    that.groupFooterTemplate = that._footerTmpl(columns, aggregates, "groupFooterTemplate", "k-group-footer", columnsLocked.length);

                    if (options.scrollable && columnsLocked.length) {
                        that.lockedGroupFooterTemplate = that._footerTmpl(columnsLocked, aggregates, "groupFooterTemplate", "k-group-footer");
                    }
                }
            },

            _footerTmpl: function (columns, aggregates, templateName, rowClass, skipGroupCells) {
                var that = this,
                settings = extend({}, kendo.Template, that.options.templateSettings),
                paramName = settings.paramName,
                html = "",
                idx,
                length,
                template,
                type,
                storage = {},
                count = 0,
                scope = {},
                groups = that._groups(),
                fieldsMap = buildEmptyAggregatesObject(aggregates),
                column;

                html += '<tr class="' + rowClass + '">';

                if (groups > 0 && !skipGroupCells) {
                    html += groupCells(groups);
                }

                if (that._hasDetails()) {
                    html += '<td class="k-hierarchy-cell">&nbsp;</td>';
                }

                for (idx = 0, length = columns.length; idx < length; idx++) {
                    column = columns[idx];
                    template = column[templateName];
                    type = typeof template;

                    html += "<td" + stringifyAttributes(column.footerAttributes) + ">";

                    if (template) {
                        if (type !== FUNCTION) {
                            scope = fieldsMap[column.field] ? extend({}, settings, { paramName: paramName + "['" + column.field + "']" }) : {};
                            template = kendo.template(template, scope);
                        }

                        storage["tmpl" + count] = template;
                        html += "#=this.tmpl" + count + "(" + paramName + ")#";
                        count++;
                    } else {
                        html += "&nbsp;";
                    }

                    html += "</td>";
                }

                html += '</tr>';

                html = kendo.template(html, settings);

                if (count > 0) {
                    return proxy(html, storage);
                }

                return html;
            },

            _detailTmpl: function (template) {
                var that = this,
                html = "",
                settings = extend({}, kendo.Template, that.options.templateSettings),
                paramName = settings.paramName,
                templateFunctionStorage = {},
                templateFunctionCount = 0,
                groups = that._groups(),
                colspan = visibleColumns(leafColumns(that.columns)).length,
                type = typeof template;

                html += '<tr class="k-detail-row">';
                if (groups > 0) {
                    html += groupCells(groups);
                }
                html += '<td class="k-hierarchy-cell"></td><td class="k-detail-cell"' + (colspan ? ' colspan="' + colspan + '"' : '') + ">";

                if (type === FUNCTION) {
                    templateFunctionStorage["tmpl" + templateFunctionCount] = template;
                    html += "#=this.tmpl" + templateFunctionCount + "(" + paramName + ")#";
                    templateFunctionCount++;
                } else {
                    html += template;
                }

                html += "</td></tr>";

                html = kendo.template(html, settings);

                if (templateFunctionCount > 0) {
                    return proxy(html, templateFunctionStorage);
                }

                return html;
            },

            _hasDetails: function () {
                var that = this;

                return that.options.detailTemplate !== null || (that._events[DETAILINIT] || []).length;
            },
            _hasFilterRow: function () {
                var filterable = this.options.filterable;
                var hasFiltering = filterable &&
                    typeof filterable.mode == STRING &&
                    filterable.mode.indexOf("row") != -1;
                var columns = this.columns;
                var columnsWithoutFiltering = $.grep(columns, function (col, idx) {
                    return col.filterable === false;
                });

                if (columns.length && columnsWithoutFiltering.length == columns.length) {
                    hasFiltering = false;
                }

                return hasFiltering;
            },

            _details: function () {
                var that = this;

                if (that.options.scrollable && that._hasDetails() && lockedColumns(that.columns).length) {
                    throw new Error("Having both detail template and locked columns is not supported");
                }

                that.table.on(CLICK + NS, ".k-hierarchy-cell .k-plus, .k-hierarchy-cell .k-minus", function (e) {
                    var button = $(this),
                    expanding = button.hasClass("k-plus"),
                    masterRow = button.closest("tr.k-master-row"),
                    detailRow,
                    detailTemplate = that.detailTemplate,
                    data,
                    hasDetails = that._hasDetails();

                    button.toggleClass("k-plus", !expanding)
                    .toggleClass("k-minus", expanding);

                    detailRow = masterRow.next();

                    if (hasDetails && !detailRow.hasClass("k-detail-row")) {
                        data = that.dataItem(masterRow);

                        detailRow = $(detailTemplate(data))
                        .addClass(masterRow.hasClass("k-alt") ? "k-alt" : "")
                        .insertAfter(masterRow);

                        that.angular("compile", function () {
                            return {
                                elements: detailRow.get(),
                                data: [{ dataItem: data}]
                            };
                        });

                        that.trigger(DETAILINIT, { masterRow: masterRow, detailRow: detailRow, data: data, detailCell: detailRow.find(".k-detail-cell") });
                    }

                    that.trigger(expanding ? DETAILEXPAND : DETAILCOLLAPSE, { masterRow: masterRow, detailRow: detailRow });
                    detailRow.toggle(expanding);

                    if (that._current) {
                        that._current.attr("aria-expanded", expanding);
                    }

                    e.preventDefault();
                    return false;
                });
            },

            dataItem: function (tr) {
                tr = $(tr)[0];
                if (!tr) {
                    return null;
                }

                var rows = this.tbody.children(),
                classesRegEx = /k-grouping-row|k-detail-row|k-group-footer/,
                idx = tr.sectionRowIndex,
                j, correctIdx;

                correctIdx = idx;

                for (j = 0; j < idx; j++) {
                    if (classesRegEx.test(rows[j].className)) {
                        correctIdx--;
                    }
                }

                return this._data[correctIdx];
            },

            expandRow: function (tr) {
                $(tr).find('> td .k-plus, > td .k-i-expand').click();
            },

            collapseRow: function (tr) {
                $(tr).find('> td .k-minus, > td .k-i-collapse').click();
            },

            _createHeaderCells: function (columns, rowSpan) {
                var that = this,
                idx,
                th,
                text,
                html = "",
                length,
                leafs = leafColumns(that.columns);

                for (idx = 0, length = columns.length; idx < length; idx++) {
                    th = columns[idx].column || columns[idx];
                    text = that._headerCellText(th);

                    var index = inArray(th, leafs);

                    if (!th.command) {
                        html += "<th role='columnheader' " + kendo.attr("field") + "='" + (th.field || "") + "' ";

                        if (rowSpan && !columns[idx].colSpan) {
                            html += " rowspan='" + rowSpan + "'";
                        }

                        if (columns[idx].colSpan > 1) {
                            html += 'colspan="' + (columns[idx].colSpan - hiddenLeafColumnsCount(th.columns)) + '" ';
                            html += kendo.attr("colspan") + "='" + columns[idx].colSpan + "'";
                        }

                        if (th.title) {
                            html += kendo.attr("title") + '="' + th.title.replace(/'/g, "\'") + '" ';
                        }

                        if (th.groupable !== undefined) {
                            html += kendo.attr("groupable") + "='" + th.groupable + "' ";
                        }

                        if (th.aggregates && th.aggregates.length) {
                            html += kendo.attr("aggregates") + "='" + th.aggregates + "'";
                        }

                        if (index > -1) {
                            html += kendo.attr("index") + "='" + index + "'";
                        }

                        html += stringifyAttributes(th.headerAttributes);

                        html += ">" + text + "</th>";
                    } else {
                        html += "<th" + stringifyAttributes(th.headerAttributes);

                        if (rowSpan && !columns[idx].colSpan) {
                            html += " rowspan='" + rowSpan + "'";
                        }

                        if (index > -1) {
                            html += kendo.attr("index") + "='" + index + "'";
                        }

                        html += ">" + text + "</th>";
                    }
                }
                return html;
            },

            _appendLockedColumnContent: function () {
                var columns = this.columns,
                idx,
                colgroup = this.table.find("colgroup"),
                cols = colgroup.find("col:not(.k-group-col,.k-hierarchy-col)"),
                length,
                lockedCols = $(),
                skipHiddenCount = 0,
                container,
                colSpan,
                spanIdx,
                colOffset = 0;

                for (idx = 0, length = columns.length; idx < length; idx++) {
                    if (columns[idx].locked) {

                        if (isVisible(columns[idx])) {
                            colSpan = 1;

                            if (columns[idx].columns) {
                                colSpan = leafColumns(columns[idx].columns).length - hiddenLeafColumnsCount(columns[idx].columns);
                            }

                            colSpan = colSpan || 1;
                            for (spanIdx = 0; spanIdx < colSpan; spanIdx++) {
                                lockedCols = lockedCols.add(cols.eq(idx + colOffset + spanIdx - skipHiddenCount));
                            }
                            colOffset += colSpan - 1;
                        } else {
                            skipHiddenCount++;
                        }
                    }
                }

                container = $('<div class="k-grid-content-locked"><table' + (isIE7 ? ' cellspacing="0"' : '') + '><colgroup/><tbody></tbody></table></div>');
                // detach is required for IE8, otherwise it switches to compatibility mode
                colgroup.detach();
                container.find("colgroup").append(lockedCols);
                colgroup.insertBefore(this.table.find("tbody"));

                this.lockedContent = container.insertBefore(this.content);
                this.lockedTable = container.children("table");
            },

            _appendLockedColumnFooter: function () {
                var that = this;
                var footer = that.footer;
                var cells = footer.find(".k-footer-template>td");
                var cols = footer.find(".k-grid-footer-wrap>table>colgroup>col");
                var html = $('<div class="k-grid-footer-locked"><table><colgroup /><tbody><tr class="k-footer-template"></tr></tbody></table></div>');
                var idx, length;
                var groups = that._groups();
                var lockedCells = $(), lockedCols = $();

                lockedCells = lockedCells.add(cells.filter(".k-group-cell"));
                for (idx = 0, length = leafColumns(lockedColumns(that.columns)).length; idx < length; idx++) {
                    lockedCells = lockedCells.add(cells.eq(idx + groups));
                }

                lockedCols = lockedCols.add(cols.filter(".k-group-col"));
                for (idx = 0, length = leafColumns(visibleLockedColumns(that.columns)).length; idx < length; idx++) {
                    lockedCols = lockedCols.add(cols.eq(idx + groups));
                }

                lockedCells.appendTo(html.find("tr"));
                lockedCols.appendTo(html.find("colgroup"));
                that.lockedFooter = html.prependTo(footer);
            },

            _appendLockedColumnHeader: function (container) {
                var that = this,
                columns = this.columns,
                idx,
                html,
                length,
                colgroup,
                tr,
                trFilter,
                table,
                header,
                filtercellCells,
                rows = [],
                skipHiddenCount = 0,
                cols = $(),
                hasFilterRow = that._hasFilterRow(),
                filterCellOffset = 0,
                filterCells = $(),
                cell,
                leafColumnsCount = 0,
                cells = $();

                colgroup = that.thead.prev().find("col:not(.k-group-col,.k-hierarchy-col)");
                header = that.thead.find("tr:first .k-header:not(.k-group-cell,.k-hierarchy-cell)");
                filtercellCells = that.thead.find(".k-filter-row").find("th:not(.k-group-cell,.k-hierarchy-cell)");

                var colOffset = 0;
                for (idx = 0, length = columns.length; idx < length; idx++) {
                    if (columns[idx].locked) {
                        cell = header.eq(idx);
                        leafColumnsCount = leafColumns(columns[idx].columns || []).length;

                        if (isVisible(columns[idx])) {
                            var colSpan;

                            if (columns[idx].columns) {
                                colSpan = leafColumnsCount - hiddenLeafColumnsCount(columns[idx].columns);
                            }

                            colSpan = colSpan || 1;
                            for (var spanIdx = 0; spanIdx < colSpan; spanIdx++) {
                                cols = cols.add(colgroup.eq(idx + colOffset + spanIdx - skipHiddenCount));
                            }
                            colOffset += colSpan - 1;
                        }

                        mapColumnToCellRows([columns[idx]], childColumnsCells(cell), rows, 0, 0);

                        leafColumnsCount = leafColumnsCount || 1;
                        for (var j = 0; j < leafColumnsCount; j++) {
                            filterCells = filterCells.add(filtercellCells.eq(filterCellOffset + j));
                        }
                        filterCellOffset += leafColumnsCount;
                    }

                    if (columns[idx].columns) {
                        skipHiddenCount += hiddenLeafColumnsCount(columns[idx].columns);
                    }

                    if (!isVisible(columns[idx])) {
                        skipHiddenCount++;
                    }
                }

                if (rows.length) {
                    html = '<div class="k-grid-header-locked" style="width:1px"><table' + (isIE7 ? ' cellspacing="0"' : '') + '><colgroup/><thead>';
                    html += new Array(rows.length + 1).join("<tr></tr>");
                    html += (hasFilterRow ? '<tr class="k-filter-row" />' : '') + '</thead></table></div>';

                    table = $(html);

                    colgroup = table.find("colgroup");
                    colgroup.append(that.thead.prev().find("col.k-group-col").add(cols));

                    tr = table.find("thead tr:not(.k-filter-row)");
                    for (idx = 0, length = rows.length; idx < length; idx++) {
                        cells = toJQuery(rows[idx]);
                        tr.eq(idx).append(that.thead.find("tr:eq(" + idx + ") .k-group-cell").add(cells));
                    }

                    var count = removeEmptyRows(this.thead);
                    if (rows.length < count) {
                        removeRowSpanValue(table, count);
                    }

                    trFilter = table.find(".k-filter-row");
                    trFilter.append(that.thead.find(".k-filter-row .k-group-cell").add(filterCells));

                    this.lockedHeader = table.prependTo(container);
                    this._syncLockedHeaderHeight();
                }
            },

            _removeLockedContainers: function () {
                var elements = this.lockedHeader
                .add(this.lockedContent)
                .add(this.lockedFooter);

                kendo.destroy(elements);
                elements.off(NS).remove();

                this.lockedHeader = this.lockedContent = this.lockedFooter = null;
                this.selectable = null;
            },

            _thead: function () {
                var that = this,
                columns = that.columns,
                hasDetails = that._hasDetails() && columns.length,
                hasFilterRow = that._hasFilterRow(),
                idx,
                length,
                html = "",
                thead = that.table.find(">thead"),
                hasTHead = that.element.find("thead:first").length > 0,
                tr,
                text,
                th;

                if (!thead.length) {
                    thead = $("<thead/>").insertBefore(that.tbody);
                }

                if (that.lockedHeader && that.thead) {
                    tr = that.thead.find("tr:has(th):not(.k-filter-row)").html("");

                    that._removeLockedContainers();
                } else if (hasTHead) {
                    tr = that.element.find("thead:first tr:has(th):not(.k-filter-row)");
                } else {
                    tr = that.element.find("tr:has(th):first");
                }

                if (!tr.length) {
                    tr = thead.children().first();
                    if (!tr.length) {
                        var rows = [{ rowSpan: 1, cells: [], index: 0}];
                        that._prepareColumns(rows, columns);

                        for (idx = 0; idx < rows.length; idx++) {
                            html += "<tr>";
                            if (hasDetails) {
                                html += '<th class="k-hierarchy-cell">&nbsp;</th>';
                            }
                            html += that._createHeaderCells(rows[idx].cells, rows[idx].rowSpan);
                            html += "</tr>";
                        }

                        tr = $(html);
                    }
                }

                if (hasFilterRow) {
                    var filterRow = $("<tr/>");
                    filterRow.addClass("k-filter-row");
                    if (hasDetails) {
                        filterRow.prepend('<th class="k-hierarchy-cell">&nbsp;</th>');
                    }

                    thead.append(filterRow);
                }

                if (!tr.children().length) {
                    html = "";
                    if (hasDetails) {
                        html += '<th class="k-hierarchy-cell">&nbsp;</th>';
                    }

                    html += that._createHeaderCells(columns);

                    tr.html(html);
                } else if (hasDetails && !tr.find(".k-hierarchy-cell")[0]) {
                    tr.prepend('<th class="k-hierarchy-cell">&nbsp;</th>');
                }

                tr.attr("role", "row").find("th").addClass("k-header");

                if (!that.options.scrollable) {
                    thead.addClass("k-grid-header");
                }

                tr.find("script").remove().end().prependTo(thead);


                if (that.thead) {
                    that._destroyColumnAttachments();
                }

                this.angular("cleanup", function () {
                    return {
                        elements: thead.find("th").get()
                    };
                });

                this.angular("compile", function () {
                    return {
                        elements: thead.find("th").get(),
                        data: map(columns, function (col) { return { column: col }; })
                    };
                });

                that.thead = thead.attr("role", "rowgroup");

                that._sortable();

                that._filterable();

                that._filterRow();

                that._scrollable();

                that._updateCols();

                that._columnMenu();

                if (this.options.scrollable && lockedColumns(this.columns).length) {

                    that._appendLockedColumnHeader(that.thead.closest(".k-grid-header"));

                    that._appendLockedColumnContent();

                    that.lockedContent.bind("DOMMouseScroll" + NS + " mousewheel" + NS, proxy(that._wheelScroll, that));

                    that._applyLockedContainersWidth();
                }

                that._updateFirstColumnClass();

                that._resizable();

                that._draggable();

                that._reorderable();

                if (that.groupable) {
                    that._attachGroupable();
                }
            },

            _updateFirstColumnClass: function () {
                var that = this,
                columns = that.columns || [],
                hasDetails = that._hasDetails() && columns.length;

                if (!hasDetails && !that._groups()) {
                    var rows = $();

                    var tr = that.thead.find(">tr:not(.k-filter-row):not(:first)");
                    columns = nonLockedColumns(columns);

                    if (tr.length && columns[0] && !columns[0].columns) {
                        rows = rows.add(tr);
                    }

                    if (that._isLocked()) {
                        tr = that.lockedHeader.find("thead>tr:not(.k-filter-row):not(:first)");
                        columns = lockedColumns(that.columns);

                        if (tr.length && columns[0] && !columns[0].columns) {
                            rows = rows.add(tr);
                        }
                    }

                    rows.each(function () {
                        var ths = $(this).find("th");
                        ths.removeClass("k-first");
                        ths.eq(0).addClass("k-first");
                    });
                }
            },

            _prepareColumns: function (rows, columns, parentCell, parentRow) {
                var row = parentRow || rows[rows.length - 1];

                var childRow = rows[row.index + 1];
                var totalColSpan = 0;

                for (var idx = 0; idx < columns.length; idx++) {
                    var cell = { column: columns[idx], colSpan: 0 };
                    row.cells.push(cell);

                    if (columns[idx].columns && columns[idx].columns.length) {
                        if (!childRow) {
                            childRow = { rowSpan: 0, cells: [], index: rows.length };
                            rows.push(childRow);
                        }
                        cell.colSpan = columns[idx].columns.length;
                        this._prepareColumns(rows, columns[idx].columns, cell, childRow);
                        totalColSpan += cell.colSpan - 1;
                        row.rowSpan = rows.length - row.index;
                    }
                }
                if (parentCell) {
                    parentCell.colSpan += totalColSpan;
                }
            },

            _wheelScroll: function (e) {
                if (e.ctrlKey) {
                    return;
                }

                var content = this.content;

                if (this.options.scrollable.virtual) {
                    content = this.virtualScrollable.verticalScrollbar;
                }

                var scrollTop = content.scrollTop(),
                delta = kendo.wheelDeltaY(e);

                if (delta) {
                    e.preventDefault();
                    //In Firefox DOMMouseScroll event cannot be canceled
                    $(e.currentTarget).one("wheel" + NS, false);

                    content.scrollTop(scrollTop + (-delta));
                }
            },

            _isLocked: function () {
                return this.lockedHeader != null;
            },

            _updateCols: function (table) {
                table = table || this.thead.parent().add(this.table);

                this._appendCols(table, this._isLocked());
            },

            _updateLockedCols: function (table) {
                if (this._isLocked()) {
                    table = table || this.lockedHeader.find("table").add(this.lockedTable);

                    normalizeCols(table, visibleLeafColumns(visibleLockedColumns(this.columns)), this._hasDetails(), this._groups());
                }
            },

            _appendCols: function (table, locked) {
                if (locked) {
                    normalizeCols(table, visibleLeafColumns(visibleNonLockedColumns(this.columns)), this._hasDetails(), 0);
                } else {
                    normalizeCols(table, visibleLeafColumns(visibleColumns(this.columns)), this._hasDetails(), this._groups());
                }
            },

            _autoColumns: function (schema) {
                if (schema && schema.toJSON) {
                    var that = this,
                    field;

                    schema = schema.toJSON();

                    for (field in schema) {
                        that.columns.push({ field: field });
                    }

                    that._thead();

                    that._templates();
                }
            },

            _rowsHtml: function (data, templates) {
                var that = this,
                html = "",
                idx,
                rowTemplate = templates.rowTemplate,
                altRowTemplate = templates.altRowTemplate,
                length;

                for (idx = 0, length = data.length; idx < length; idx++) {
                    if (idx % 2) {
                        html += altRowTemplate(data[idx]);
                    } else {
                        html += rowTemplate(data[idx]);
                    }

                    that._data.push(data[idx]);
                }

                return html;
            },

            _groupRowHtml: function (group, colspan, level, groupHeaderBuilder, templates, skipColspan) {
                var that = this,
                html = "",
                idx,
                length,
                field = group.field,
                column = grep(that.columns, function (column) { return column.field == field; })[0] || {},
                template = column.groupHeaderTemplate,
                text = (column.title || field) + ': ' + formatGroupValue(group.value, column.format, column.values),
                footerDefaults = that._groupAggregatesDefaultObject || {},
                aggregates = extend(footerDefaults, group.aggregates),
                data = extend({}, { field: group.field, value: group.value, aggregates: aggregates }, group.aggregates[group.field]),
                rowTemplate = templates.rowTemplate,
                altRowTemplate = templates.altRowTemplate,
                groupFooterTemplate = templates.groupFooterTemplate,
                groupItems = group.items;

                if (template) {
                    text = typeof template === FUNCTION ? template(data) : kendo.template(template)(data);
                }

                html += groupHeaderBuilder(colspan, level, text);

                if (group.hasSubgroups) {
                    for (idx = 0, length = groupItems.length; idx < length; idx++) {
                        html += that._groupRowHtml(groupItems[idx], skipColspan ? colspan : colspan - 1, level + 1, groupHeaderBuilder, templates, skipColspan);
                    }
                } else {
                    html += that._rowsHtml(groupItems, templates);
                }

                if (groupFooterTemplate) {
                    html += groupFooterTemplate(aggregates);
                }
                return html;
            },

            collapseGroup: function (group) {
                group = $(group);

                var level,
                groupable = this.options.groupable,
                showFooter = groupable.showFooter,
                footerCount = showFooter ? 0 : 1,
                offset,
                relatedGroup = $(),
                idx,
                length,
                tr;

                if (this._isLocked()) {
                    if (!group.closest("div").hasClass("k-grid-content-locked")) {
                        relatedGroup = group.nextAll("tr");
                        group = this.lockedTable.find(">tbody>tr:eq(" + group.index() + ")");
                    } else {
                        relatedGroup = this.tbody.children("tr:eq(" + group.index() + ")").nextAll("tr");
                    }
                }

                level = group.find(".k-group-cell").length;
                group.find(".k-icon").addClass("k-i-expand").removeClass("k-i-collapse");
                group.find("td:first").attr("aria-expanded", false);
                group = group.nextAll("tr");

                for (idx = 0, length = group.length; idx < length; idx++) {
                    tr = group.eq(idx);
                    offset = tr.find(".k-group-cell").length;

                    if (tr.hasClass("k-grouping-row")) {
                        footerCount++;
                    } else if (tr.hasClass("k-group-footer")) {
                        footerCount--;
                    }

                    if (offset <= level || (tr.hasClass("k-group-footer") && footerCount < 0)) {
                        break;
                    }

                    tr.hide();
                    relatedGroup.eq(idx).hide();
                }
            },

            expandGroup: function (group) {
                group = $(group);

                var that = this,
                level,
                tr,
                offset,
                relatedGroup = $(),
                idx,
                length,
                groupsCount = 1;

                if (this._isLocked()) {
                    if (!group.closest("div").hasClass("k-grid-content-locked")) {
                        relatedGroup = group.nextAll("tr");
                        group = this.lockedTable.find(">tbody>tr:eq(" + group.index() + ")");
                    } else {
                        relatedGroup = this.tbody.children("tr:eq(" + group.index() + ")").nextAll("tr");
                    }
                }

                level = group.find(".k-group-cell").length;
                group.find(".k-icon").addClass("k-i-collapse").removeClass("k-i-expand");
                group.find("td:first").attr("aria-expanded", true);
                group = group.nextAll("tr");

                for (idx = 0, length = group.length; idx < length; idx++) {
                    tr = group.eq(idx);
                    offset = tr.find(".k-group-cell").length;
                    if (offset <= level) {
                        break;
                    }

                    if (offset == level + 1 && !tr.hasClass("k-detail-row")) {
                        tr.show();
                        relatedGroup.eq(idx).show();

                        if (tr.hasClass("k-grouping-row") && tr.find(".k-icon").hasClass("k-i-collapse")) {
                            that.expandGroup(tr);
                        }

                        if (tr.hasClass("k-master-row") && tr.find(".k-icon").hasClass("k-minus")) {
                            tr.next().show();
                            relatedGroup.eq(idx + 1).show();
                        }
                    }

                    if (tr.hasClass("k-grouping-row")) {
                        groupsCount++;
                    }

                    if (tr.hasClass("k-group-footer")) {
                        if (groupsCount == 1) {
                            tr.show();
                            relatedGroup.eq(idx).show();
                        } else {
                            groupsCount--;
                        }
                    }
                }
            },

            _updateHeader: function (groups) {
                var that = this,
                container = that._isLocked() ? that.lockedHeader.find("thead") : that.thead,
                filterCells = container.find("tr.k-filter-row").find("th.k-group-cell").length,
                length = container.find("tr:first").find("th.k-group-cell").length,
                rows = container.children("tr:not(:first)").filter(function () {
                    return !$(this).children(":visible").length;
                });

                if (groups > length) {
                    $(new Array(groups - length + 1).join('<th class="k-group-cell k-header">&nbsp;</th>')).prependTo(container.children("tr:not(.k-filter-row)"));
                    if (that.element.is(":visible")) {
                        rows.find("th.k-group-cell").hide();
                    }
                } else if (groups < length) {
                    container.find("tr").each(function () {
                        $(this).find("th.k-group-cell")
                        .filter(":eq(" + groups + ")," + ":gt(" + groups + ")").remove();
                    });
                }
                if (groups > filterCells) {
                    $(new Array(groups - filterCells + 1).join('<th class="k-group-cell k-header">&nbsp;</th>')).prependTo(container.find(".k-filter-row"));
                }
            },

            _firstDataItem: function (data, grouped) {
                if (data && grouped) {
                    if (data.hasSubgroups) {
                        data = this._firstDataItem(data.items[0], grouped);
                    } else {
                        data = data.items[0];
                    }
                }
                return data;
            },

            _updateTablesWidth: function () {
                var that = this,
                tables;

                if (!that._isLocked()) {
                    return;
                }

                tables =
                $(">.k-grid-footer>.k-grid-footer-wrap>table", that.wrapper)
                .add(that.thead.parent())
                .add(that.table);

                that._footerWidth = tableWidth(tables.eq(0));
                tables.width(that._footerWidth);

                tables =
                $(">.k-grid-footer>.k-grid-footer-locked>table", that.wrapper)
                .add(that.lockedHeader.find(">table"))
                .add(that.lockedTable);

                tables.width(tableWidth(tables.eq(0)));
            },

            hideColumn: function (column) {
                var that = this,
                cell,
                tables,
                idx,
                cols,
                colWidth,
                position,
                row,
                width = 0,
                parents = [],
                headerCellIndex,
                length,
                footer = that.footer || that.wrapper.find(".k-grid-footer"),
                columns = that.columns,
                visibleLocked = that.lockedHeader ? leafDataCells(that.lockedHeader.find(">table>thead")).filter(":visible").length : 0,
                columnIndex;

                if (typeof column == "number") {
                    column = columns[column];
                } else if (isPlainObject(column)) {
                    column = grep(flatColumns(columns), function (item) {
                        return item === column;
                    })[0];
                } else {
                    column = grep(flatColumns(columns), function (item) {
                        return item.field === column;
                    })[0];
                }

                if (!column || !isVisible(column)) {
                    return;
                }


                if (column.columns && column.columns.length) {
                    position = columnVisiblePosition(column, columns);

                    setColumnVisibility(column, false);

                    setCellVisibility(elements($(">table>thead", that.lockedHeader), that.thead, ">tr:eq(" + position.row + ")>th"), position.cell, false);

                    for (idx = 0; idx < column.columns.length; idx++) {
                        this.hideColumn(column.columns[idx]);
                    }

                    that.trigger(COLUMNHIDE, { column: column });

                    return;
                }

                columnIndex = inArray(column, visibleColumns(leafColumns(columns)));
                setColumnVisibility(column, false);

                that._setParentsVisibility(column, false);

                that._templates();

                that._updateCols();
                that._updateLockedCols();

                var container = that.thead;

                headerCellIndex = columnIndex;
                if (that.lockedHeader && visibleLocked > columnIndex) {
                    container = that.lockedHeader.find(">table>thead");
                } else {
                    headerCellIndex -= visibleLocked;
                }

                cell = leafDataCells(container).filter(":visible").eq(headerCellIndex);
                cell[0].style.display = "none";

                setCellVisibility(elements($(">table>thead", that.lockedHeader), that.thead, ">tr.k-filter-row>th"), columnIndex, false);
                if (footer[0]) {
                    that._updateCols(footer.find(">.k-grid-footer-wrap>table"));
                    that._updateLockedCols(footer.find(">.k-grid-footer-locked>table"));
                    setCellVisibility(footer.find(".k-footer-template>td"), columnIndex, false);
                }

                if (that.lockedTable && visibleLocked > columnIndex) {
                    hideColumnCells(that.lockedTable.find(">tbody>tr"), columnIndex);
                } else {
                    hideColumnCells(that.tbody.children(), columnIndex - visibleLocked);
                }

                if (that.lockedTable) {
                    that._updateTablesWidth();
                    that._applyLockedContainersWidth();
                    that._syncLockedContentHeight();
                    that._syncLockedHeaderHeight();
                    that._syncLockedFooterHeight();
                } else {
                    cols = that.thead.prev().find("col");
                    for (idx = 0, length = cols.length; idx < length; idx += 1) {
                        colWidth = cols[idx].style.width;
                        if (colWidth && colWidth.indexOf("%") == -1) {
                            width += parseInt(colWidth, 10);
                        } else {
                            width = 0;
                            break;
                        }
                    }

                    tables = $(">.k-grid-header table:first,>.k-grid-footer table:first", that.wrapper)
                .add(that.table);
                    that._footerWidth = null;

                    if (width) {
                        tables.width(width);
                        that._footerWidth = width;
                    }

                    if (browser.msie && browser.version == 8) {
                        tables.css("display", "inline-table");
                        setTimeout(function () {
                            tables.css("display", "table");
                        }, 1);
                    }
                }

                that._updateFirstColumnClass();
                that.trigger(COLUMNHIDE, { column: column });
            },

            _setParentsVisibility: function (column, visible) {
                var columns = this.columns;
                var idx;
                var parents = [];
                var parent;
                var position;
                var cell;
                var colSpan;

                var predicate = visible ?
                function (p) { return visibleColumns(p.columns).length && p.hidden; } :
                function (p) { return !visibleColumns(p.columns).length && !p.hidden; };


                if (columnParents(column, columns, parents) && parents.length) {
                    for (idx = parents.length - 1; idx >= 0; idx--) {
                        parent = parents[idx];
                        position = columnPosition(parent, columns);
                        cell = elements($(">table>thead", this.lockedHeader), this.thead, ">tr:eq(" + position.row + ")>th:not(.k-group-cell):not(.k-hierarchy-cell)").eq(position.cell);

                        if (predicate(parent)) {
                            setColumnVisibility(parent, visible);
                            cell[0].style.display = visible ? "" : "none";
                        }

                        if (cell.filter("[" + kendo.attr("colspan") + "]").length) {
                            colSpan = parseInt(cell.attr(kendo.attr("colspan")), 10);
                            cell[0].colSpan = (colSpan - hiddenLeafColumnsCount(parent.columns)) || 1;
                        }
                    }
                }
            },

            showColumn: function (column) {
                var that = this,
                idx,
                length,
                cell,
                tables,
                width,
                row,
                headerCellIndex,
                position,
                colWidth,
                cols,
                columns = that.columns,
                footer = that.footer || that.wrapper.find(".k-grid-footer"),
                lockedColumnsCount = that.lockedHeader ? leafDataCells(that.lockedHeader.find(">table>thead")).length : 0,
                columnIndex;

                if (typeof column == "number") {
                    column = columns[column];
                } else if (isPlainObject(column)) {
                    column = grep(flatColumns(columns), function (item) {
                        return item === column;
                    })[0];
                } else {
                    column = grep(flatColumns(columns), function (item) {
                        return item.field === column;
                    })[0];
                }

                if (!column || isVisible(column)) {
                    return;
                }

                if (column.columns && column.columns.length) {
                    position = columnVisiblePosition(column, columns);

                    setColumnVisibility(column, true);

                    setCellVisibility(elements($(">table>thead", that.lockedHeader), that.thead, ">tr:eq(" + position.row + ")>th"), position.cell, true);

                    for (idx = 0; idx < column.columns.length; idx++) {
                        this.showColumn(column.columns[idx]);
                    }

                    that.trigger(COLUMNSHOW, { column: column });

                    return;
                }

                columnIndex = inArray(column, leafColumns(columns));

                setColumnVisibility(column, true);

                that._setParentsVisibility(column, true);

                that._templates();
                that._updateCols();
                that._updateLockedCols();

                var container = that.thead;

                headerCellIndex = columnIndex;
                if (that.lockedHeader && lockedColumnsCount > columnIndex) {
                    container = that.lockedHeader.find(">table>thead");
                } else {
                    headerCellIndex -= lockedColumnsCount;
                }

                cell = leafDataCells(container).eq(headerCellIndex);
                cell[0].style.display = "";

                setCellVisibility(elements($(">table>thead", that.lockedHeader), that.thead, ">tr.k-filter-row>th"), columnIndex, true);
                if (footer[0]) {
                    that._updateCols(footer.find(">.k-grid-footer-wrap>table"));
                    that._updateLockedCols(footer.find(">.k-grid-footer-locked>table"));
                    setCellVisibility(footer.find(".k-footer-template>td"), columnIndex, true);
                }

                if (that.lockedTable && lockedColumnsCount > columnIndex) {
                    showColumnCells(that.lockedTable.find(">tbody>tr"), columnIndex);
                } else {
                    showColumnCells(that.tbody.children(), columnIndex - lockedColumnsCount);
                }

                if (that.lockedTable) {
                    that._updateTablesWidth();
                    that._applyLockedContainersWidth();
                    that._syncLockedContentHeight();
                    that._syncLockedHeaderHeight();
                } else {
                    tables = $(">.k-grid-header table:first,>.k-grid-footer table:first", that.wrapper).add(that.table);
                    if (!column.width) {
                        tables.width("");
                    } else {
                        width = 0;
                        cols = that.thead.prev().find("col");
                        for (idx = 0, length = cols.length; idx < length; idx += 1) {
                            colWidth = cols[idx].style.width;
                            if (colWidth.indexOf("%") > -1) {
                                width = 0;
                                break;
                            }
                            width += parseInt(colWidth, 10);
                        }

                        that._footerWidth = null;
                        if (width) {
                            tables.width(width);
                            that._footerWidth = width;
                        }
                    }
                }

                that._updateFirstColumnClass();

                that.trigger(COLUMNSHOW, { column: column });
            },

            _progress: function (toggle) {
                var element = this.element;

                if (this.lockedContent) {
                    element = this.wrapper;
                } else if (this.element.is("table")) {
                    element = this.element.parent();
                } else if (this.content && this.content.length) {
                    element = this.content;
                }

                kendo.ui.progress(element, toggle);
            },

            _resize: function () {
                if (this.content) {
                    this._setContentHeight();
                    this._setContentWidth();
                }
            },

            _isActiveInTable: function () {
                var active = activeElement();

                return this.table[0] === active ||
                $.contains(this.table[0], active) ||
                (this._isLocked() &&
                    (this.lockedTable[0] === active || $.contains(this.lockedTable[0], active))
                );
            },

            refresh: function (e) {
                var that = this,
                length,
                idx,
                html = "",
                data = that.dataSource.view(),
                navigatable = that.options.navigatable,
                currentIndex,
                current = $(that.current()),
                isCurrentInHeader = false,
                groups = (that.dataSource.group() || []).length,
                colspan = groups + visibleLeafColumns(visibleColumns(that.columns)).length;

                if (e && e.action === "itemchange" && that.editable) { // skip rebinding if editing is in progress
                    return;
                }

                e = e || {};

                if (that.trigger("dataBinding", { action: e.action || "rebind", index: e.index, items: e.items })) {
                    return;
                }

                that._angularItems("cleanup");

                if (navigatable && (that._isActiveInTable() || (that._editContainer && that._editContainer.data("kendoWindow")))) {
                    isCurrentInHeader = current.is("th");
                    currentIndex = 0;
                    if (isCurrentInHeader) {
                        currentIndex = that.thead.find("th:not(.k-group-cell)").index(current);
                    }
                }

                that._destroyEditable();

                that._progress(false);

                that._hideResizeHandle();

                that._data = [];

                if (!that.columns.length) {
                    that._autoColumns(that._firstDataItem(data[0], groups));
                    colspan = groups + that.columns.length;
                }

                that._group = groups > 0 || that._group;

                if (that._group) {
                    that._templates();
                    that._updateCols();
                    that._updateLockedCols();
                    that._updateHeader(groups);
                    that._group = groups > 0;
                }

                that._renderContent(data, colspan, groups);

                that._renderLockedContent(data, colspan, groups);

                that._footer();

                that._setContentHeight();

                that._setContentWidth();

                if (that.lockedTable) {
                    //requires manual trigger of scroll to sync both tables
                    if (that.options.scrollable.virtual) {
                        that.content.find(">.k-virtual-scrollable-wrap").trigger("scroll");
                    } else {
                        that.content.trigger("scroll");
                    }
                }

                if (currentIndex >= 0) {
                    that._removeCurrent();
                    if (!isCurrentInHeader) {
                        that.current(that.table.add(that.lockedTable).find(FIRSTNAVITEM).first());
                    } else {
                        that.current(that.thead.find("th:not(.k-group-cell)").eq(currentIndex));
                    }

                    if (that._current) {
                        focusTable(that._current.closest("table")[0], true);
                    }
                }

                if (that.touchScroller) {
                    that.touchScroller.contentResized();
                }

                if (that.selectable) {
                    that.selectable.resetTouchEvents();
                }

                that._angularItems("compile");

                that.trigger(DATABOUND);
            },

            _renderContent: function (data, colspan, groups) {
                var that = this,
                idx,
                length,
                html = "",
                isLocked = that.lockedContent != null,
                templates = {
                    rowTemplate: that.rowTemplate,
                    altRowTemplate: that.altRowTemplate,
                    groupFooterTemplate: that.groupFooterTemplate
                };

                colspan = isLocked ? colspan - visibleLeafColumns(visibleLockedColumns(that.columns)).length : colspan;

                if (groups > 0) {

                    colspan = isLocked ? colspan - groups : colspan;

                    if (that.detailTemplate) {
                        colspan++;
                    }

                    if (that.groupFooterTemplate) {
                        that._groupAggregatesDefaultObject = buildEmptyAggregatesObject(that.dataSource.aggregate());
                    }

                    for (idx = 0, length = data.length; idx < length; idx++) {
                        html += that._groupRowHtml(data[idx], colspan, 0, isLocked ? groupRowLockedContentBuilder : groupRowBuilder, templates, isLocked);
                    }
                } else {
                    html += that._rowsHtml(data, templates);
                }

                that.tbody = appendContent(that.tbody, that.table, html);
            },

            _renderLockedContent: function (data, colspan, groups) {
                var html = "",
               idx,
               length,
               templates = {
                   rowTemplate: this.lockedRowTemplate,
                   altRowTemplate: this.lockedAltRowTemplate,
                   groupFooterTemplate: this.lockedGroupFooterTemplate
               };

                if (this.lockedContent) {

                    var table = this.lockedTable;

                    if (groups > 0) {
                        colspan = colspan - visibleColumns(leafColumns(nonLockedColumns(this.columns))).length;
                        for (idx = 0, length = data.length; idx < length; idx++) {
                            html += this._groupRowHtml(data[idx], colspan, 0, groupRowBuilder, templates);
                        }
                    } else {
                        html = this._rowsHtml(data, templates);
                    }

                    appendContent(table.children("tbody"), table, html);

                    this._syncLockedContentHeight();
                }
            },

            _adjustRowsHeight: function (table1, table2) {
                var rows = table1[0].rows,
               length = rows.length,
               idx,
               rows2 = table2[0].rows,
               containers = table1.add(table2),
               containersLength = containers.length,
               heights = [];

                for (idx = 0; idx < length; idx++) {
                    if (!rows2[idx]) {
                        break;
                    }

                    if (rows[idx].style.height) {
                        rows[idx].style.height = rows2[idx].style.height = "";
                    }

                    var offsetHeight1 = rows[idx].offsetHeight;
                    var offsetHeight2 = rows2[idx].offsetHeight;
                    var height = 0;

                    if (offsetHeight1 > offsetHeight2) {
                        height = offsetHeight1;
                    } else if (offsetHeight1 < offsetHeight2) {
                        height = offsetHeight2;
                    }

                    heights.push(height);
                }

                for (idx = 0; idx < containersLength; idx++) {
                    containers[idx].style.display = "none";
                }

                for (idx = 0; idx < length; idx++) {
                    if (heights[idx]) {
                        rows[idx].style.height = rows2[idx].style.height = heights[idx] + "px";
                    }
                }

                for (idx = 0; idx < containersLength; idx++) {
                    containers[idx].style.display = "";
                }
            }
        });

        if (kendo.ExcelMixin) {
            kendo.ExcelMixin.extend(Grid.prototype);
        }

        if (kendo.PDFMixin) {
            kendo.PDFMixin.extend(Grid.prototype);
        }

        function syncTableHeight(table1, table2) {
            table1 = table1[0];
            table2 = table2[0];

            if (table1.rows.length !== table2.rows.length) {
                var lockedHeigth = table1.offsetHeight;
                var tableHeigth = table2.offsetHeight;

                var row;
                var diff;
                if (lockedHeigth > tableHeigth) {
                    row = table2.rows[table2.rows.length - 1];

                    if (filterRowRegExp.test(row.className)) {
                        row = table2.rows[table2.rows.length - 2];
                    }

                    diff = lockedHeigth - tableHeigth;
                } else {
                    row = table1.rows[table1.rows.length - 1];

                    if (filterRowRegExp.test(row.className)) {
                        row = table1.rows[table1.rows.length - 2];
                    }

                    diff = tableHeigth - lockedHeigth;
                }
                row.style.height = row.offsetHeight + diff + "px";
            }
        }

        function adjustRowHeight(row1, row2) {
            var height;
            var offsetHeight1 = row1.offsetHeight;
            var offsetHeight2 = row2.offsetHeight;

            if (offsetHeight1 > offsetHeight2) {
                height = offsetHeight1 + "px";
            } else if (offsetHeight1 < offsetHeight2) {
                height = offsetHeight2 + "px";
            }

            if (height) {
                row1.style.height = row2.style.height = height;
            }
        }


        function getCommand(commands, name) {
            var idx, length, command;

            if (typeof commands === STRING && commands === name) {
                return commands;
            }

            if (isPlainObject(commands) && commands.name === name) {
                return commands;
            }

            if (isArray(commands)) {
                for (idx = 0, length = commands.length; idx < length; idx++) {
                    command = commands[idx];

                    if ((typeof command === STRING && command === name) || (command.name === name)) {
                        return command;
                    }
                }
            }
            return null;
        }

        function focusTable(table, direct) {
            var msie = browser.msie;
            if (direct === true) {
                table = $(table);
                var condition = true || msie && table.parent().is(".k-grid-content,.k-grid-header-wrap"),
               scrollTop, scrollLeft;
                if (condition) {
                    scrollTop = table.parent().scrollTop();
                    scrollLeft = table.parent().scrollLeft();
                }

                if (msie) {
                    try {
                        //The setActive method does not cause the document to scroll to the active object in the current page
                        table[0].setActive();
                    } catch (e) {
                        table[0].focus();
                    }
                } else {
                    table[0].focus(); //because preventDefault bellow, IE cannot focus the table alternative is unselectable=on
                }

                if (condition) {
                    table.parent().scrollTop(scrollTop);
                    table.parent().scrollLeft(scrollLeft);
                }

            } else {
                $(table).one("focusin", function (e) { e.preventDefault(); }).focus();
            }
        }

        function tableClick(e) {
            var currentTarget = $(e.currentTarget),
           isHeader = currentTarget.is("th"),
           table = this.table.add(this.lockedTable),
           headerTable = this.thead.parent().add($(">table", this.lockedHeader)),
           isInput = $(e.target).is(":button,a,:input,a>.k-icon,textarea,span.k-icon,span.k-link,.k-input,.k-multiselect-wrap"),
           currentTable = currentTarget.closest("table")[0];

            if (kendo.support.touch || (isInput && currentTarget.find(kendo.roleSelector("filtercell")).length)) {
                return;
            }

            if (currentTable !== table[0] && currentTable !== table[1] && currentTable !== headerTable[0] && currentTable !== headerTable[1]) {
                return;
            }

            if ($(e.target).is("a.k-i-collapse, a.k-i-expand")) {
                return;
            }

            this.current(currentTarget);

            if (isHeader || !isInput) {
                setTimeout(function () {
                    //Do not focus if widget, because in IE8 a DDL will be closed
                    if (!(isIE8 && $(kendo._activeElement()).hasClass("k-widget"))) {
                        //DOMElement.focus() only for header, because IE doesn't really focus the table
                        focusTable(currentTable, true);
                    }
                });
            }

            if (isHeader) {
                e.preventDefault(); //if any problem occurs, call preventDefault only for the clicked header links
            }
        }

        function verticalTable(current, downTable, upTable, up) {
            current = $(current);
            if (up) {
                var temp = downTable;
                downTable = upTable;
                upTable = temp;
            }

            if (downTable.not(current).length != downTable.length) {
                return current;
            }

            return current[0] == upTable[0] ?
                   downTable.eq(0) : downTable.eq(1);
        }

        function moveVertical(current, currentTable, dataTable, headerTable, up) {
            var row, index;
            var nextFn = up ? "prevAll" : "nextAll";

            if (current) {
                row = current.parent()[nextFn](NAVROW).first();
                if (!row[0] && (up || current.is("th")) || (!up && current[0].rowSpan > 1)) {
                    currentTable = verticalTable(currentTable, dataTable, headerTable, up);
                    focusTable(currentTable);
                    if (up && !current.is(".k-header")) {
                        return leafDataCells(currentTable.find("thead:first")).eq(current.index());
                    }
                    row = currentTable.find((up ? ">thead>" : ">tbody>") + NAVROW).first();
                }

                if (!up && current[0].colSpan > 1 && current.is(".k-header")) { // is not leaf header column
                    current = childColumnsCells(current).eq(1);
                } else {
                    if (current.is(".k-header") && up) {
                        var parents = parentColumnsCells(current);
                        current = parents.eq(parents.length - 2);
                    } else {
                        index = current.attr(kendo.attr("index"));
                        if (index === undefined || up) {
                            index = current.index();
                        }
                        current = row.children().eq(index);
                    }
                }

                if (!current[0] || !current.is(NAVCELL)) {
                    current = row.children(NAVCELL).first();
                }
            } else {
                current = dataTable.find(FIRSTNAVITEM);
            }

            return current;
        }

        function moveLeft(current, currentTable, dataTable, headerTable, relatedRow) {
            var isLocked = dataTable.length > 1;

            if (current) {
                if (current.prevAll(":visible")[0]) {
                    current = current.prevAll(DATA_CELL).first();
                } else if (isLocked) {
                    if (currentTable == dataTable[1]) {
                        focusTable(dataTable[0]);
                        current = relatedRow(current.parent()).children(DATA_CELL).last();
                    } else if (currentTable == headerTable[1]) {
                        focusTable(headerTable[0]);
                        current = headerTable.eq(0).find("tr>" + DATA_CELL).last();
                    }
                }
            } else {
                current = dataTable.find(FIRSTNAVITEM);
            }

            return current;
        }

        function moveRight(current, currentTable, dataTable, headerTable, relatedRow) {
            var isLocked = dataTable.length > 1;

            if (current) {
                if (current.nextAll(":visible")[0]) {
                    current = current.nextAll(DATA_CELL).first();
                } else if (isLocked) {
                    if (currentTable == dataTable[0]) {
                        focusTable(dataTable[1]);
                        current = relatedRow(current.parent()).children(DATA_CELL).first();
                    } else if (currentTable == headerTable[0]) {
                        focusTable(headerTable[1]);
                        current = headerTable.eq(1).find("tr>" + DATA_CELL).first();
                    }
                }
            } else {
                current = dataTable.find(FIRSTNAVITEM);
            }

            return current;
        }

        function tabNext(current, currentTable, dataTable, relatedRow, back) {
            var isLocked = dataTable.length == 2;
            var switchRow = true;
            var next = back ? current.prevAll(DATA_CELL + ":first") : current.nextAll(":visible:first");

            if (!next.length) {
                next = current.parent();
                if (isLocked) {
                    switchRow = (back && currentTable == dataTable[0]) || (!back && currentTable == dataTable[1]);
                    next = relatedRow(next);
                }

                if (switchRow) {
                    next = next[back ? "prevAll" : "nextAll"]("tr:not(.k-grouping-row):not(.k-detail-row):visible:first");
                }
                next = next.children(DATA_CELL + (back ? ":last" : ":first"));
            }

            return next;
        }

        function groupRowBuilder(colspan, level, text) {
            return '<tr class="k-grouping-row">' + groupCells(level) +
           '<td colspan="' + colspan + '" aria-expanded="true">' +
           '<p class="k-reset">' +
           '<a class="k-icon k-i-collapse" href="#" tabindex="-1"></a>' + text +
       '</p></td></tr>';
        }

        function groupRowLockedContentBuilder(colspan, level, text) {
            return '<tr class="k-grouping-row">' +
           '<td colspan="' + colspan + '" aria-expanded="true">' +
           '<p class="k-reset">&nbsp;</p></td></tr>';
        }

        ui.plugin(Grid);
        ui.plugin(VirtualScrollable);

    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        CHANGE = "change",
        CANCEL = "cancel",
        DATABOUND = "dataBound",
        DATABINDING = "dataBinding",
        Widget = kendo.ui.Widget,
        keys = kendo.keys,
        FOCUSSELECTOR = ">*",
        PROGRESS = "progress",
        ERROR = "error",
        FOCUSED = "k-state-focused",
        SELECTED = "k-state-selected",
        KEDITITEM = "k-edit-item",
        STRING = "string",
        EDIT = "edit",
        REMOVE = "remove",
        SAVE = "save",
        CLICK = "click",
        NS = ".kendoListView",
        proxy = $.proxy,
        activeElement = kendo._activeElement,
        progress = kendo.ui.progress,
        DataSource = kendo.data.DataSource;

        var ListView = kendo.ui.DataBoundWidget.extend({
            init: function (element, options) {
                var that = this;

                options = $.isArray(options) ? { dataSource: options} : options;

                Widget.fn.init.call(that, element, options);

                options = that.options;

                that.wrapper = element = that.element;

                if (element[0].id) {
                    that._itemId = element[0].id + "_lv_active";
                }

                that._element();

                that._dataSource();

                that._templates();

                that._navigatable();

                that._selectable();

                that._pageable();

                that._crudHandlers();

                if (that.options.autoBind) {
                    that.dataSource.fetch();
                }

                kendo.notify(that);
            },

            events: [
            CHANGE,
            CANCEL,
            DATABINDING,
            DATABOUND,
            EDIT,
            REMOVE,
            SAVE
        ],

            options: {
                name: "ListView",
                autoBind: true,
                selectable: false,
                navigatable: false,
                template: "",
                altTemplate: "",
                editTemplate: ""
            },

            setOptions: function (options) {
                Widget.fn.setOptions.call(this, options);

                this._templates();
            },

            _templates: function () {
                var options = this.options;

                this.template = kendo.template(options.template || "");
                this.altTemplate = kendo.template(options.altTemplate || options.template);
                this.editTemplate = kendo.template(options.editTemplate || "");
            },

            _item: function (action) {
                return this.element.children()[action]();
            },

            items: function () {
                return this.element.children();
            },

            dataItem: function (element) {
                var attr = kendo.attr("uid");
                var uid = $(element).closest("[" + attr + "]").attr(attr);

                return this.dataSource.getByUid(uid);
            },

            setDataSource: function (dataSource) {
                this.options.dataSource = dataSource;
                this._dataSource();

                if (this.options.autoBind) {
                    dataSource.fetch();
                }
            },

            _unbindDataSource: function () {
                var that = this;

                that.dataSource.unbind(CHANGE, that._refreshHandler)
                            .unbind(PROGRESS, that._progressHandler)
                            .unbind(ERROR, that._errorHandler);
            },

            _dataSource: function () {
                var that = this;

                if (that.dataSource && that._refreshHandler) {
                    that._unbindDataSource();
                } else {
                    that._refreshHandler = proxy(that.refresh, that);
                    that._progressHandler = proxy(that._progress, that);
                    that._errorHandler = proxy(that._error, that);
                }

                that.dataSource = DataSource.create(that.options.dataSource)
                                .bind(CHANGE, that._refreshHandler)
                                .bind(PROGRESS, that._progressHandler)
                                .bind(ERROR, that._errorHandler);
            },

            _progress: function () {
                progress(this.element, true);
            },

            _error: function () {
                progress(this.element, false);
            },

            _element: function () {
                this.element.addClass("k-widget k-listview").attr("role", "listbox");
            },

            refresh: function (e) {
                var that = this,
                view = that.dataSource.view(),
                data,
                items,
                item,
                html = "",
                idx,
                length,
                template = that.template,
                altTemplate = that.altTemplate,
                active = activeElement();

                e = e || {};

                if (e.action === "itemchange") {
                    if (!that._hasBindingTarget() && !that.editable) {
                        data = e.items[0];
                        item = that.items().filter("[" + kendo.attr("uid") + "=" + data.uid + "]");

                        if (item.length > 0) {
                            idx = item.index();

                            that.angular("cleanup", function () {
                                return { elements: [item] };
                            });

                            item.replaceWith(template(data));
                            item = that.items().eq(idx);
                            item.attr(kendo.attr("uid"), data.uid);

                            that.angular("compile", function () {
                                return { elements: [item], data: [{ dataItem: data}] };
                            });

                            that.trigger("itemChange", {
                                item: item,
                                data: data
                            });
                        }
                    }

                    return;
                }

                if (that.trigger(DATABINDING, { action: e.action || "rebind", items: e.items, index: e.index })) {
                    return;
                }

                that._angularItems("cleanup");

                that._destroyEditable();

                for (idx = 0, length = view.length; idx < length; idx++) {
                    if (idx % 2) {
                        html += altTemplate(view[idx]);
                    } else {
                        html += template(view[idx]);
                    }
                }

                that.element.html(html);

                items = that.items();
                for (idx = 0, length = view.length; idx < length; idx++) {
                    items.eq(idx).attr(kendo.attr("uid"), view[idx].uid)
                             .attr("role", "option")
                             .attr("aria-selected", "false");
                }

                if (that.element[0] === active && that.options.navigatable) {
                    that.current(items.eq(0));
                }

                that._angularItems("compile");

                that.trigger(DATABOUND);
            },

            _pageable: function () {
                var that = this,
                pageable = that.options.pageable,
                settings,
                pagerId;

                if ($.isPlainObject(pageable)) {
                    pagerId = pageable.pagerId;
                    settings = $.extend({}, pageable, {
                        dataSource: that.dataSource,
                        pagerId: null
                    });

                    that.pager = new kendo.ui.Pager($("#" + pagerId), settings);
                }
            },

            _selectable: function () {
                var that = this,
                multi,
                current,
                selectable = that.options.selectable,
                navigatable = that.options.navigatable;

                if (selectable) {
                    multi = kendo.ui.Selectable.parseOptions(selectable).multiple;

                    that.selectable = new kendo.ui.Selectable(that.element, {
                        aria: true,
                        multiple: multi,
                        filter: FOCUSSELECTOR,
                        change: function () {
                            that.trigger(CHANGE);
                        }
                    });

                    if (navigatable) {
                        that.element.on("keydown" + NS, function (e) {
                            if (e.keyCode === keys.SPACEBAR) {
                                current = that.current();
                                if (e.target == e.currentTarget) {
                                    e.preventDefault();
                                }
                                if (multi) {
                                    if (!e.ctrlKey) {
                                        that.selectable.clear();
                                    } else {
                                        if (current && current.hasClass(SELECTED)) {
                                            current.removeClass(SELECTED);
                                            return;
                                        }
                                    }
                                } else {
                                    that.selectable.clear();
                                }

                                that.selectable.value(current);
                            }
                        });
                    }
                }
            },

            current: function (candidate) {
                var that = this,
                element = that.element,
                current = that._current,
                id = that._itemId;

                if (candidate === undefined) {
                    return current;
                }

                if (current && current[0]) {
                    if (current[0].id === id) {
                        current.removeAttr("id");
                    }

                    current.removeClass(FOCUSED);
                    element.removeAttr("aria-activedescendant");
                }

                if (candidate && candidate[0]) {
                    id = candidate[0].id || id;

                    that._scrollTo(candidate[0]);

                    element.attr("aria-activedescendant", id);
                    candidate.addClass(FOCUSED).attr("id", id);
                }

                that._current = candidate;
            },

            _scrollTo: function (element) {
                var that = this,
                container,
                UseJQueryoffset = false,
                SCROLL = "scroll";

                if (that.wrapper.css("overflow") == "auto" || that.wrapper.css("overflow") == SCROLL) {
                    container = that.wrapper[0];
                } else {
                    container = window;
                    UseJQueryoffset = true;
                }

                var scrollDirectionFunc = function (direction, dimension) {

                    var elementOffset = UseJQueryoffset ? $(element).offset()[direction.toLowerCase()] : element["offset" + direction],
                    elementDimension = element["client" + dimension],
                    containerScrollAmount = $(container)[SCROLL + direction](),
                    containerDimension = $(container)[dimension.toLowerCase()]();

                    if (elementOffset + elementDimension > containerScrollAmount + containerDimension) {
                        $(container)[SCROLL + direction](elementOffset + elementDimension - containerDimension);
                    } else if (elementOffset < containerScrollAmount) {
                        $(container)[SCROLL + direction](elementOffset);
                    }
                };

                scrollDirectionFunc("Top", "Height");
                scrollDirectionFunc("Left", "Width");
            },

            _navigatable: function () {
                var that = this,
                navigatable = that.options.navigatable,
                element = that.element,
                clickCallback = function (e) {
                    that.current($(e.currentTarget));
                    if (!$(e.target).is(":button,a,:input,a>.k-icon,textarea")) {
                        element.focus();
                    }
                };

                if (navigatable) {
                    that._tabindex();
                    element.on("focus" + NS, function () {
                        var current = that._current;
                        if (!current || !current.is(":visible")) {
                            current = that._item("first");
                        }

                        that.current(current);
                    })
                    .on("focusout" + NS, function () {
                        if (that._current) {
                            that._current.removeClass(FOCUSED);
                        }
                    })
                    .on("keydown" + NS, function (e) {
                        var key = e.keyCode,
                            current = that.current(),
                            target = $(e.target),
                            canHandle = !target.is(":button,textarea,a,a>.t-icon,input"),
                            isTextBox = target.is(":text"),
                            preventDefault = kendo.preventDefault,
                            editItem = element.find("." + KEDITITEM),
                            active = activeElement(), idx;

                        if ((!canHandle && !isTextBox && keys.ESC != key) || (isTextBox && keys.ESC != key && keys.ENTER != key)) {
                            return;
                        }

                        if (keys.UP === key || keys.LEFT === key) {
                            if (current) {
                                current = current.prev();
                            }

                            that.current(!current || !current[0] ? that._item("last") : current);
                            preventDefault(e);
                        } else if (keys.DOWN === key || keys.RIGHT === key) {
                            if (current) {
                                current = current.next();
                            }
                            that.current(!current || !current[0] ? that._item("first") : current);
                            preventDefault(e);
                        } else if (keys.PAGEUP === key) {
                            that.current(null);
                            that.dataSource.page(that.dataSource.page() - 1);
                            preventDefault(e);
                        } else if (keys.PAGEDOWN === key) {
                            that.current(null);
                            that.dataSource.page(that.dataSource.page() + 1);
                            preventDefault(e);
                        } else if (keys.HOME === key) {
                            that.current(that._item("first"));
                            preventDefault(e);
                        } else if (keys.END === key) {
                            that.current(that._item("last"));
                            preventDefault(e);
                        } else if (keys.ENTER === key) {
                            if (editItem.length !== 0 && (canHandle || isTextBox)) {
                                idx = that.items().index(editItem);
                                if (active) {
                                    active.blur();
                                }
                                that.save();
                                var focusAgain = function () {
                                    that.element.trigger("focus");
                                    that.current(that.items().eq(idx));
                                };
                                that.one("dataBound", focusAgain);
                            } else if (that.options.editTemplate !== "") {
                                that.edit(current);
                            }
                        } else if (keys.ESC === key) {
                            editItem = element.find("." + KEDITITEM);
                            if (editItem.length === 0) {
                                return;
                            }
                            idx = that.items().index(editItem);
                            that.cancel();
                            that.element.trigger("focus");
                            that.current(that.items().eq(idx));
                        }
                    });

                    element.on("mousedown" + NS + " touchstart" + NS, FOCUSSELECTOR, proxy(clickCallback, that));
                }
            },

            clearSelection: function () {
                var that = this;
                that.selectable.clear();
                that.trigger(CHANGE);
            },

            select: function (items) {
                var that = this,
               selectable = that.selectable;

                items = $(items);
                if (items.length) {
                    if (!selectable.options.multiple) {
                        selectable.clear();
                        items = items.first();
                    }
                    selectable.value(items);
                    return;
                }

                return selectable.value();
            },

            _destroyEditable: function () {
                var that = this;
                if (that.editable) {
                    that.editable.destroy();
                    delete that.editable;
                }
            },

            _modelFromElement: function (element) {
                var uid = element.attr(kendo.attr("uid"));

                return this.dataSource.getByUid(uid);
            },

            _closeEditable: function (validate) {
                var that = this,
               editable = that.editable,
               data,
               item,
               index,
               template = that.template,
               valid = true;

                if (editable) {
                    if (validate) {
                        valid = editable.end();
                    }

                    if (valid) {
                        if (editable.element.index() % 2) {
                            template = that.altTemplate;
                        }

                        data = that._modelFromElement(editable.element);
                        that._destroyEditable();

                        index = editable.element.index();
                        editable.element.replaceWith(template(data));
                        item = that.items().eq(index);
                        item.attr(kendo.attr("uid"), data.uid);

                        if (that._hasBindingTarget()) {
                            kendo.bind(item, data);
                        }
                    }
                }

                return valid;
            },

            edit: function (item) {
                var that = this,
               data = that._modelFromElement(item),
               container,
               uid = data.uid,
               index;

                that.cancel();

                item = that.items().filter("[" + kendo.attr("uid") + "=" + uid + "]");
                index = item.index();
                item.replaceWith(that.editTemplate(data));
                container = that.items().eq(index).addClass(KEDITITEM).attr(kendo.attr("uid"), data.uid);
                that.editable = container.kendoEditable({
                    model: data,
                    clearContainer: false,
                    errorTemplate: false,
                    target: that
                }).data("kendoEditable");

                that.trigger(EDIT, { model: data, item: container });
            },

            save: function () {
                var that = this,
               editable = that.editable,
               model;

                if (!editable) {
                    return;
                }

                editable = editable.element;
                model = that._modelFromElement(editable);

                if (!that.trigger(SAVE, { model: model, item: editable }) && that._closeEditable(true)) {
                    that.dataSource.sync();
                }
            },

            remove: function (item) {
                var that = this,
               dataSource = that.dataSource,
               data = that._modelFromElement(item);

                if (!that.trigger(REMOVE, { model: data, item: item })) {
                    item.hide();
                    dataSource.remove(data);
                    dataSource.sync();
                }
            },

            add: function () {
                var that = this,
               dataSource = that.dataSource,
               index = dataSource.indexOf((dataSource.view() || [])[0]);

                if (index < 0) {
                    index = 0;
                }

                that.cancel();
                dataSource.insert(index, {});
                that.edit(that.element.children().first());
            },

            cancel: function () {
                var that = this,
               dataSource = that.dataSource;

                if (that.editable) {
                    var container = that.editable.element;
                    var model = that._modelFromElement(container);

                    if (!that.trigger(CANCEL, { model: model, container: container })) {
                        dataSource.cancelChanges(model);
                        that._closeEditable(false);
                    }
                }
            },

            _crudHandlers: function () {
                var that = this,
               clickNS = CLICK + NS;

                that.element.on(clickNS, ".k-edit-button", function (e) {
                    var item = $(this).closest("[" + kendo.attr("uid") + "]");
                    that.edit(item);
                    e.preventDefault();
                });

                that.element.on(clickNS, ".k-delete-button", function (e) {
                    var item = $(this).closest("[" + kendo.attr("uid") + "]");
                    that.remove(item);
                    e.preventDefault();
                });

                that.element.on(clickNS, ".k-update-button", function (e) {
                    that.save();
                    e.preventDefault();
                });

                that.element.on(clickNS, ".k-cancel-button", function (e) {
                    that.cancel();
                    e.preventDefault();
                });
            },

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that._unbindDataSource();

                that._destroyEditable();

                that.element.off(NS);

                if (that.pager) {
                    that.pager.destroy();
                }

                kendo.destroy(that.element);
            }
        });

        kendo.ui.plugin(ListView);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        logToConsole = kendo.logToConsole,
        rFileExtension = /\.([^\.]+)$/,
        NS = ".kendoUpload",
        SELECT = "select",
        UPLOAD = "upload",
        SUCCESS = "success",
        ERROR = "error",
        COMPLETE = "complete",
        CANCEL = "cancel",
        PROGRESS = "progress",
        REMOVE = "remove";

        var Upload = Widget.extend({
            init: function (element, options) {
                var that = this;

                Widget.fn.init.call(that, element, options);

                that.name = element.name;
                that.multiple = that.options.multiple;
                that.localization = that.options.localization;

                var activeInput = that.element;
                that.wrapper = activeInput.closest(".k-upload");
                if (that.wrapper.length === 0) {
                    that.wrapper = that._wrapInput(activeInput);
                }

                that._activeInput(activeInput);
                that.toggle(that.options.enabled);

                var ns = that._ns = NS + "-" + kendo.guid();
                activeInput.closest("form")
                .on("submit" + ns, $.proxy(that._onParentFormSubmit, that))
                .on("reset" + ns, $.proxy(that._onParentFormReset, that));

                if (that.options.async.saveUrl) {
                    that._module = that._supportsFormData() ?
                new formDataUploadModule(that) :
                new iframeUploadModule(that);
                    that._async = true;

                    var initialFiles = that.options.files;
                    if (initialFiles.length > 0) {
                        that._renderInitialFiles(initialFiles);
                    }

                } else {
                    that._module = new syncUploadModule(that);
                }

                if (that._supportsDrop()) {
                    that._setupDropZone();
                }

                that.wrapper
            .on("click", ".k-upload-action", $.proxy(that._onFileAction, that))
            .on("click", ".k-upload-selected", $.proxy(that._onUploadSelected, that));

                if (that.element.val()) {
                    that._onInputChange({ target: that.element });
                }
            },

            events: [
            SELECT,
            UPLOAD,
            SUCCESS,
            ERROR,
            COMPLETE,
            CANCEL,
            PROGRESS,
            REMOVE
        ],

            options: {
                name: "Upload",
                enabled: true,
                multiple: true,
                showFileList: true,
                template: "",
                files: [],
                async: {
                    removeVerb: "POST",
                    autoUpload: true,
                    withCredentials: true
                },
                localization: {
                    "select": "Select files...",
                    "cancel": "Cancel",
                    "retry": "Retry",
                    "remove": "Remove",
                    "uploadSelectedFiles": "Upload files",
                    "dropFilesHere": "drop files here to upload",
                    "statusUploading": "uploading",
                    "statusUploaded": "uploaded",
                    "statusWarning": "warning",
                    "statusFailed": "failed",
                    "headerStatusUploading": "Uploading...",
                    "headerStatusUploaded": "Done"
                }
            },

            setOptions: function (options) {
                var that = this,
                activeInput = that.element;

                Widget.fn.setOptions.call(that, options);

                that.multiple = that.options.multiple;

                activeInput.attr("multiple", that._supportsMultiple() ? that.multiple : false);
                that.toggle(that.options.enabled);
            },

            enable: function (enable) {
                enable = typeof (enable) === "undefined" ? true : enable;
                this.toggle(enable);
            },

            disable: function () {
                this.toggle(false);
            },

            toggle: function (enable) {
                enable = typeof (enable) === "undefined" ? enable : !enable;
                this.wrapper.toggleClass("k-state-disabled", enable);
                this.element.prop("disabled", enable);
            },

            destroy: function () {
                var that = this;

                $(document)
                .add($(".k-dropzone", that.wrapper))
                .add(that.wrapper.closest("form"))
                .off(that._ns);

                $(that.element).off(NS);

                Widget.fn.destroy.call(that);
            },

            _addInput: function (sourceInput) {
                //check if source input is a DOM element. Required for some unit tests
                if (!sourceInput[0].nodeType) {
                    return;
                }

                var that = this,
                input = sourceInput.clone().val("");

                input
                .insertAfter(that.element)
                .data("kendoUpload", that);

                $(that.element)
                .hide()
                .attr("tabindex", "-1")
                .removeAttr("id")
                .off(NS);

                that._activeInput(input);
                that.element.focus();
            },

            _activeInput: function (input) {
                var that = this,
                wrapper = that.wrapper;

                that.element = input;

                input
                .attr("multiple", that._supportsMultiple() ? that.multiple : false)
                .attr("autocomplete", "off")
                .on("click" + NS, function (e) {
                    if (wrapper.hasClass("k-state-disabled")) {
                        e.preventDefault();
                    }
                })
                .on("focus" + NS, function () {
                    $(this).parent().addClass("k-state-focused");
                })
                .on("blur" + NS, function () {
                    $(this).parent().removeClass("k-state-focused");
                })
                .on("change" + NS, $.proxy(that._onInputChange, that))
                .on("keydown" + NS, $.proxy(that._onInputKeyDown, that));
            },

            _onInputKeyDown: function (e) {
                var that = this;
                var firstButton = that.wrapper.find(".k-upload-action:first");

                if (e.keyCode === kendo.keys.TAB && firstButton.length > 0) {
                    e.preventDefault();

                    firstButton.focus();
                }
            },

            _onInputChange: function (e) {
                var that = this;
                var input = $(e.target);
                var files = assignGuidToFiles(that._inputFiles(input), that._isAsyncNonBatch());

                var prevented = that.trigger(SELECT, { files: files });

                if (prevented) {
                    that._addInput(input);
                    input.remove();
                } else {
                    that._module.onSelect({ target: input }, files);
                }
            },

            _onDrop: function (e) {
                var dt = e.originalEvent.dataTransfer;
                var that = this;
                var droppedFiles = dt.files;
                var files = assignGuidToFiles(getAllFileInfo(droppedFiles), that._isAsyncNonBatch());

                stopEvent(e);

                if (droppedFiles.length > 0) {
                    var prevented = that.trigger(SELECT, { files: files });

                    if (!prevented) {
                        that._module.onSelect({ target: $(".k-dropzone", that.wrapper) }, files);
                    }
                }
            },

            _isAsyncNonBatch: function () {
                return (this._async && !this.options.async.batch) || false;
            },

            _renderInitialFiles: function (files) {
                var that = this;
                var idx = 0;
                files = assignGuidToFiles(files, true);

                for (idx = 0; idx < files.length; idx++) {
                    var currentFile = files[idx];

                    var fileEntry = that._enqueueFile(currentFile.name, { fileNames: [currentFile] });
                    fileEntry.addClass("k-file-success").data("files", [files[idx]]);

                    $(".k-progress", fileEntry).width('100%');
                    $(".k-upload-status", fileEntry).prepend("<span class='k-upload-pct'>100%</span>");

                    that._fileAction(fileEntry, REMOVE);
                }
            },

            _prepareTemplateData: function (name, data) {
                var filesData = data.fileNames,
                templateData = {},
                totalSize = 0,
                idx = 0;

                for (idx = 0; idx < filesData.length; idx++) {
                    totalSize += filesData[idx].size;
                }

                templateData.name = name;
                templateData.size = totalSize;
                templateData.files = data.fileNames;

                return templateData;
            },

            _prepareDefaultFileEntryTemplate: function (name, data) {
                var extension = "";
                var defaultTemplate = $("<li class='k-file'>" +
                    "<span class='k-progress'></span>" +
                    "<span class='k-icon'></span>" +
                    "<span class='k-filename' title='" + name + "'>" + name + "</span>" +
                    "<strong class='k-upload-status'></strong>" +
                    "</li>");

                if (data.fileNames.length == 1 && !!data.fileNames[0].extension) {
                    extension = data.fileNames[0].extension.substring(1);
                    $('.k-icon', defaultTemplate).addClass('k-i-' + extension);
                }
                return defaultTemplate;
            },

            _enqueueFile: function (name, data) {
                var that = this;
                var existingFileEntries;
                var fileEntry;
                var fileUid = data.fileNames[0].uid;
                var fileList = $(".k-upload-files", that.wrapper);
                var options = that.options;
                var template = options.template;
                var templateData;

                if (fileList.length === 0) {
                    fileList = $("<ul class='k-upload-files k-reset'></ul>").appendTo(that.wrapper);
                    if (!that.options.showFileList) {
                        fileList.hide();
                    }

                    that.wrapper.removeClass("k-upload-empty");
                }

                existingFileEntries = $(".k-file", fileList);

                if (!template) {
                    fileEntry = that._prepareDefaultFileEntryTemplate(name, data);
                } else {
                    templateData = that._prepareTemplateData(name, data);
                    template = kendo.template(template);

                    fileEntry = $("<li class='k-file'>" + template(templateData) + "</li>");
                    fileEntry.find(".k-upload-action").addClass("k-button k-button-bare");
                }

                fileEntry
                .attr(kendo.attr("uid"), fileUid)
                .appendTo(fileList)
                .data(data);

                if (!that._async) {
                    $(".k-progress", fileEntry).width('100%');
                }

                if (!that.multiple && existingFileEntries.length > 0) {
                    that._module.onRemove({ target: $(existingFileEntries, that.wrapper) });
                }

                return fileEntry;
            },

            _removeFileEntry: function (fileEntry) {
                var that = this;
                var fileList = fileEntry.closest(".k-upload-files");
                var allFiles;
                var allCompletedFiles;

                fileEntry.remove();
                allFiles = $(".k-file", fileList);
                allCompletedFiles = $(".k-file-success, .k-file-error", fileList);

                if (allCompletedFiles.length === allFiles.length) {
                    this._hideUploadButton();
                }

                if (allFiles.length === 0) {
                    fileList.remove();
                    that.wrapper.addClass("k-upload-empty");
                    that._hideHeaderUploadstatus();
                }
            },

            _fileAction: function (fileElement, actionKey) {
                var classDictionary = { remove: "k-delete", cancel: "k-cancel", retry: "k-retry" };
                var iconsClassDictionary = { remove: "k-i-close", cancel: "k-i-close", retry: "k-i-refresh" };

                if (!classDictionary.hasOwnProperty(actionKey)) {
                    return;
                }

                this._clearFileAction(fileElement);

                if (!this.options.template) {
                    fileElement.find(".k-upload-status .k-upload-action").remove();
                    fileElement.find(".k-upload-status").append(
                    this._renderAction(classDictionary[actionKey], this.localization[actionKey], iconsClassDictionary[actionKey])
                );
                } else {
                    fileElement.find(".k-upload-action")
                           .addClass("k-button k-button-bare")
                           .append("<span class='k-icon " + iconsClassDictionary[actionKey] + " " + classDictionary[actionKey] +
                                   "' title='" + this.localization[actionKey] + "'></span>")
                           .show();
                }
            },

            _fileState: function (fileEntry, stateKey) {
                var localization = this.localization,
                states = {
                    uploading: {
                        text: localization.statusUploading
                    },
                    uploaded: {
                        text: localization.statusUploaded
                    },
                    failed: {
                        text: localization.statusFailed
                    }
                },
                currentState = states[stateKey];

                if (currentState) {
                    $(".k-icon:not(.k-delete, .k-cancel, .k-retry)", fileEntry).text(currentState.text);
                }
            },

            _renderAction: function (actionClass, actionText, iconClass) {
                if (actionClass !== "") {
                    return $(
                "<button type='button' class='k-button k-button-bare k-upload-action'>" +
                    "<span class='k-icon " + iconClass + " " + actionClass + "' title='" + actionText + "'></span>" +
                "</button>"
                );
                }
                else {
                    return $(
                "<button type='button' class='k-button'>" +
                    actionText +
                "</button>"
                );
                }
            },

            _clearFileAction: function (fileElement) {
                $(".k-upload-action", fileElement).empty().hide();
            },

            _onFileAction: function (e) {
                var that = this;

                if (!that.wrapper.hasClass("k-state-disabled")) {
                    var button = $(e.target).closest(".k-upload-action"),
                    icon = button.find(".k-icon"),
                    fileEntry = button.closest(".k-file"),
                    eventArgs = { files: fileEntry.data("fileNames") };

                    if (icon.hasClass("k-delete")) {
                        if (!that.trigger(REMOVE, eventArgs)) {
                            that._module.onRemove({ target: $(fileEntry, that.wrapper) }, eventArgs.data);
                        }
                    } else if (icon.hasClass("k-cancel")) {
                        that.trigger(CANCEL, eventArgs);
                        that._module.onCancel({ target: $(fileEntry, that.wrapper) });
                        this._checkAllComplete();
                        that._updateHeaderUploadStatus();
                    } else if (icon.hasClass("k-retry")) {
                        $(".k-warning", fileEntry).remove();
                        that._module.onRetry({ target: $(fileEntry, that.wrapper) });
                    }
                }

                return false;
            },

            _onUploadSelected: function () {
                var that = this;
                var wrapper = that.wrapper;

                if (!wrapper.hasClass("k-state-disabled")) {
                    this._module.onSaveSelected();
                }

                return false;
            },

            _onFileProgress: function (e, percentComplete) {
                var progressPct;

                if (!this.options.template) {
                    progressPct = $(".k-upload-pct", e.target);
                    if (progressPct.length === 0) {
                        $(".k-upload-status", e.target).prepend("<span class='k-upload-pct'></span>");
                    }

                    $(".k-upload-pct", e.target).text(percentComplete + "%");
                    $(".k-progress", e.target).width(percentComplete + "%");
                } else {
                    $(".k-progress", e.target).width(percentComplete + "%");
                }

                this.trigger(PROGRESS, {
                    files: getFileEntry(e).data("fileNames"),
                    percentComplete: percentComplete
                });
            },

            _onUploadSuccess: function (e, response, xhr) {
                var fileEntry = getFileEntry(e);

                this._fileState(fileEntry, "uploaded");
                fileEntry.removeClass('k-file-progress').addClass('k-file-success');
                this._updateHeaderUploadStatus();

                this.trigger(SUCCESS, {
                    files: fileEntry.data("fileNames"),
                    response: response,
                    operation: "upload",
                    XMLHttpRequest: xhr
                });

                if (this._supportsRemove()) {
                    this._fileAction(fileEntry, REMOVE);
                } else {
                    this._clearFileAction(fileEntry);
                }

                this._checkAllComplete();
            },

            _onUploadError: function (e, xhr) {
                var fileEntry = getFileEntry(e);
                var uploadPercentage = $('.k-upload-pct', fileEntry);

                this._fileState(fileEntry, "failed");
                fileEntry.removeClass('k-file-progress').addClass('k-file-error');
                $('.k-progress', fileEntry).width("100%");

                if (uploadPercentage.length > 0) {
                    uploadPercentage.empty().removeClass('k-upload-pct').addClass('k-icon k-warning');
                } else {
                    $('.k-upload-status', fileEntry).prepend("<span class='k-icon k-warning'></span>");
                }

                this._updateHeaderUploadStatus();
                this._fileAction(fileEntry, "retry");

                this.trigger(ERROR, {
                    operation: "upload",
                    files: fileEntry.data("fileNames"),
                    XMLHttpRequest: xhr
                });

                logToConsole("Server response: " + xhr.responseText);

                this._checkAllComplete();
            },

            _showUploadButton: function () {
                var uploadButton = $(".k-upload-selected", this.wrapper);
                if (uploadButton.length === 0) {
                    uploadButton =
                    this._renderAction("", this.localization.uploadSelectedFiles)
                    .addClass("k-upload-selected");
                }

                this.wrapper.append(uploadButton);
            },

            _hideUploadButton: function () {
                $(".k-upload-selected", this.wrapper).remove();
            },

            _showHeaderUploadStatus: function () {
                var localization = this.localization;
                var dropZone = $(".k-dropzone", this.wrapper);
                var headerUploadStatus = $('.k-upload-status-total', this.wrapper);

                if (headerUploadStatus.length !== 0) {
                    headerUploadStatus.remove();
                }

                headerUploadStatus = '<strong class="k-upload-status k-upload-status-total">' + localization.headerStatusUploading +
            '<span class="k-icon k-loading">' + localization.statusUploading + '</span>' +
            '</strong>';

                if (dropZone.length > 0) {
                    dropZone.append(headerUploadStatus);
                } else {
                    $('.k-upload-button', this.wrapper).after(headerUploadStatus);
                }
            },

            _updateHeaderUploadStatus: function () {
                var that = this;
                var localization = that.localization;
                var currentlyUploading = $('.k-file', that.wrapper).not('.k-file-success, .k-file-error');
                var failedUploads;
                var headerUploadStatus;
                var headerUploadStatusIcon;

                if (currentlyUploading.length === 0) {
                    failedUploads = $('.k-file.k-file-error', that.wrapper);

                    headerUploadStatus = $('.k-upload-status-total', that.wrapper);
                    headerUploadStatusIcon = $('.k-icon', headerUploadStatus)
                                              .removeClass('k-loading')
                                              .addClass((failedUploads.length !== 0) ? 'k-warning' : "k-i-tick")
                                              .text((failedUploads.length !== 0) ? localization.statusWarning : localization.statusUploaded);

                    headerUploadStatus.text(that.localization.headerStatusUploaded)
                                  .append(headerUploadStatusIcon);
                }
            },

            _hideHeaderUploadstatus: function () {
                $('.k-upload-status-total', this.wrapper).remove();
            },

            _onParentFormSubmit: function () {
                var upload = this,
                element = upload.element;

                if (typeof this._module.onAbort !== 'undefined') {
                    this._module.onAbort();
                }

                if (!element.value) {
                    var input = $(element);

                    // Prevent submitting an empty input
                    input.attr("disabled", "disabled");

                    window.setTimeout(function () {
                        // Restore the input so the Upload remains functional
                        // in case the user cancels the form submit
                        input.removeAttr("disabled");
                    }, 0);
                }
            },

            _onParentFormReset: function () {
                $(".k-upload-files", this.wrapper).remove();
            },

            _supportsFormData: function () {
                return typeof (FormData) != "undefined";
            },

            _supportsMultiple: function () {
                var windows = this._userAgent().indexOf("Windows") > -1;

                return !kendo.support.browser.opera &&
                   !(kendo.support.browser.safari && windows);
            },

            _supportsDrop: function () {
                var userAgent = this._userAgent().toLowerCase(),
                isChrome = /chrome/.test(userAgent),
                isSafari = !isChrome && /safari/.test(userAgent),
                isWindowsSafari = isSafari && /windows/.test(userAgent);

                return !isWindowsSafari && this._supportsFormData() && (this.options.async.saveUrl);
            },

            _userAgent: function () {
                return navigator.userAgent;
            },

            _setupDropZone: function () {
                var that = this;

                $(".k-upload-button", this.wrapper)
                .wrap("<div class='k-dropzone'></div>");

                var ns = that._ns;
                var dropZone = $(".k-dropzone", that.wrapper)
                .append($("<em>" + that.localization.dropFilesHere + "</em>"))
                .on("dragenter" + ns, stopEvent)
                .on("dragover" + ns, function (e) { e.preventDefault(); })
                .on("drop" + ns, $.proxy(this._onDrop, this));

                bindDragEventWrappers(dropZone, ns,
                function () { dropZone.addClass("k-dropzone-hovered"); },
                function () { dropZone.removeClass("k-dropzone-hovered"); });

                bindDragEventWrappers($(document), ns,
                function () {
                    dropZone.addClass("k-dropzone-active");
                    dropZone.closest('.k-upload').removeClass('k-upload-empty');
                },
                function () {
                    dropZone.removeClass("k-dropzone-active");
                    if ($('li.k-file', dropZone.closest('.k-upload')).length === 0) {
                        dropZone.closest('.k-upload').addClass('k-upload-empty');
                    }
                });
            },

            _supportsRemove: function () {
                return !!this.options.async.removeUrl;
            },

            _submitRemove: function (fileNames, data, onSuccess, onError) {
                var upload = this,
                removeField = upload.options.async.removeField || "fileNames",
                params = $.extend(data, getAntiForgeryTokens());

                params[removeField] = fileNames;

                jQuery.ajax({
                    type: this.options.async.removeVerb,
                    dataType: "json",
                    dataFilter: normalizeJSON,
                    url: this.options.async.removeUrl,
                    traditional: true,
                    data: params,
                    success: onSuccess,
                    error: onError
                });
            },

            _wrapInput: function (input) {
                var that = this;
                var options = that.options;

                input.wrap("<div class='k-widget k-upload k-header'><div class='k-button k-upload-button'></div></div>");

                if (!options.async.saveUrl) {
                    input.closest(".k-upload").addClass("k-upload-sync");
                }

                input.closest(".k-upload").addClass("k-upload-empty");

                input.closest(".k-button")
                .append("<span>" + this.localization.select + "</span>");

                return input.closest(".k-upload");
            },

            _checkAllComplete: function () {
                if ($(".k-file.k-file-progress", this.wrapper).length === 0) {
                    this.trigger(COMPLETE);
                }
            },

            _inputFiles: function (sourceInput) {
                return inputFiles(sourceInput);
            }
        });

        // Synchronous upload module
        var syncUploadModule = function (upload) {
            this.name = "syncUploadModule";
            this.element = upload.wrapper;
            this.upload = upload;
            this.element
            .closest("form")
                .attr("enctype", "multipart/form-data")
                .attr("encoding", "multipart/form-data");
        };

        syncUploadModule.prototype = {
            onSelect: function (e, files) {
                var upload = this.upload;
                var sourceInput = $(e.target);

                upload._addInput(sourceInput);

                var file = upload._enqueueFile(getFileName(sourceInput), {
                    "relatedInput": sourceInput, "fileNames": files
                });

                upload._fileAction(file, REMOVE);
            },

            onRemove: function (e) {
                var fileEntry = getFileEntry(e);
                fileEntry.data("relatedInput").remove();

                this.upload._removeFileEntry(fileEntry);
            }
        };

        var iframeUploadModule = function (upload) {
            this.name = "iframeUploadModule";
            this.element = upload.wrapper;
            this.upload = upload;
            this.iframes = [];
        };

        Upload._frameId = 0;

        iframeUploadModule.prototype = {
            onSelect: function (e, files) {
                var upload = this.upload,
                sourceInput = $(e.target);

                var fileEntry = this.prepareUpload(sourceInput, files);

                if (upload.options.async.autoUpload) {
                    this.performUpload(fileEntry);
                } else {
                    if (upload._supportsRemove()) {
                        this.upload._fileAction(fileEntry, REMOVE);
                    }

                    upload._showUploadButton();
                }
            },

            prepareUpload: function (sourceInput, files) {
                var upload = this.upload;
                var activeInput = $(upload.element);
                var name = upload.options.async.saveField || sourceInput.attr("name");

                upload._addInput(sourceInput);

                sourceInput.attr("name", name);

                var iframe = this.createFrame(upload.name + "_" + Upload._frameId++);
                this.registerFrame(iframe);

                var form = this.createForm(upload.options.async.saveUrl, iframe.attr("name"))
                .append(activeInput);

                var fileEntry = upload._enqueueFile(
                getFileName(sourceInput),
                { "frame": iframe, "relatedInput": activeInput, "fileNames": files });

                iframe
                .data({ "form": form, "file": fileEntry });

                return fileEntry;
            },

            performUpload: function (fileEntry) {
                var e = { files: fileEntry.data("fileNames") },
                iframe = fileEntry.data("frame"),
                upload = this.upload;

                if (!upload.trigger(UPLOAD, e)) {
                    upload._hideUploadButton();
                    upload._showHeaderUploadStatus();

                    iframe.appendTo(document.body);

                    var form = iframe.data("form")
                    .attr("action", upload.options.async.saveUrl)
                    .appendTo(document.body);

                    e.data = $.extend({}, e.data, getAntiForgeryTokens());
                    for (var key in e.data) {
                        var dataInput = form.find("input[name='" + key + "']");
                        if (dataInput.length === 0) {
                            dataInput = $("<input>", { type: "hidden", name: key })
                            .prependTo(form);
                        }
                        dataInput.val(e.data[key]);
                    }

                    upload._fileAction(fileEntry, CANCEL);
                    upload._fileState(fileEntry, "uploading");
                    $(fileEntry).removeClass("k-file-error").addClass("k-file-progress");

                    iframe
                    .one("load", $.proxy(this.onIframeLoad, this));

                    form[0].submit();
                } else {
                    upload._removeFileEntry(iframe.data("file"));
                    this.cleanupFrame(iframe);
                    this.unregisterFrame(iframe);
                }
            },

            onSaveSelected: function () {
                var module = this;

                $(".k-file", this.element).each(function () {
                    var fileEntry = $(this),
                    started = isFileUploadStarted(fileEntry);

                    if (!started) {
                        module.performUpload(fileEntry);
                    }
                });
            },

            onIframeLoad: function (e) {
                var iframe = $(e.target),
                responseText;

                try {
                    responseText = iframe.contents().text();
                } catch (ex) {
                    responseText = "Error trying to get server response: " + ex;
                }

                this.processResponse(iframe, responseText);
            },

            processResponse: function (iframe, responseText) {
                var fileEntry = iframe.data("file"),
                module = this,
                fakeXHR = {
                    responseText: responseText
                };
                tryParseJSON(responseText,
                function (jsonResult) {
                    $.extend(fakeXHR, { statusText: "OK", status: "200" });
                    module.upload._onFileProgress({ target: $(fileEntry, module.upload.wrapper) }, 100);
                    module.upload._onUploadSuccess({ target: $(fileEntry, module.upload.wrapper) }, jsonResult, fakeXHR);

                    module.cleanupFrame(iframe);
                    module.unregisterFrame(iframe);
                },
                function () {
                    $.extend(fakeXHR, { statusText: "error", status: "500" });
                    module.upload._onUploadError({ target: $(fileEntry, module.upload.wrapper) }, fakeXHR);
                }
            );
            },

            onCancel: function (e) {
                var iframe = $(e.target).data("frame");

                this.stopFrameSubmit(iframe);
                this.cleanupFrame(iframe);
                this.unregisterFrame(iframe);
                this.upload._removeFileEntry(iframe.data("file"));
            },

            onRetry: function (e) {
                var fileEntry = getFileEntry(e);
                this.performUpload(fileEntry);
            },

            onRemove: function (e, data) {
                var fileEntry = getFileEntry(e);

                var iframe = fileEntry.data("frame");
                if (iframe) {
                    this.unregisterFrame(iframe);
                    this.upload._removeFileEntry(fileEntry);
                    this.cleanupFrame(iframe);
                } else {
                    removeUploadedFile(fileEntry, this.upload, data);
                }
            },

            onAbort: function () {
                var element = this.element,
                module = this;

                $.each(this.iframes, function () {
                    $("input", this.data("form")).appendTo(element);
                    module.stopFrameSubmit(this[0]);
                    this.data("form").remove();
                    this.remove();
                });

                this.iframes = [];
            },

            createFrame: function (id) {
                return $(
                "<iframe" +
                " name='" + id + "'" +
                " id='" + id + "'" +
                " style='display:none;' />"
            );
            },

            createForm: function (action, target) {
                return $(
                "<form enctype='multipart/form-data' method='POST'" +
                " action='" + action + "'" +
                " target='" + target + "'" +
                "/>");
            },

            stopFrameSubmit: function (frame) {
                if (typeof (frame.stop) != "undefined") {
                    frame.stop();
                } else if (frame.document) {
                    frame.document.execCommand("Stop");
                }
            },

            registerFrame: function (frame) {
                this.iframes.push(frame);
            },

            unregisterFrame: function (frame) {
                this.iframes = $.grep(this.iframes, function (value) {
                    return value.attr("name") != frame.attr("name");
                });
            },

            cleanupFrame: function (frame) {
                var form = frame.data("form");

                frame.data("file").data("frame", null);

                setTimeout(function () {
                    form.remove();
                    frame.remove();
                }, 1);
            }
        };

        // FormData upload module
        var formDataUploadModule = function (upload) {
            this.name = "formDataUploadModule";
            this.element = upload.wrapper;
            this.upload = upload;
        };

        formDataUploadModule.prototype = {
            onSelect: function (e, files) {
                var upload = this.upload,
                module = this,
                sourceElement = $(e.target),
                fileEntries = this.prepareUpload(sourceElement, files);

                $.each(fileEntries, function () {
                    if (upload.options.async.autoUpload) {
                        module.performUpload(this);
                    } else {
                        if (upload._supportsRemove()) {
                            upload._fileAction(this, REMOVE);
                        }
                        upload._showUploadButton();
                    }
                });
            },

            prepareUpload: function (sourceElement, files) {
                var fileEntries = this.enqueueFiles(files);

                if (sourceElement.is("input")) {
                    $.each(fileEntries, function () {
                        $(this).data("relatedInput", sourceElement);
                    });
                    sourceElement.data("relatedFileEntries", fileEntries);
                    this.upload._addInput(sourceElement);
                }

                return fileEntries;
            },

            enqueueFiles: function (files) {
                var upload = this.upload,
                name,
                i,
                filesLength = files.length,
                currentFile,
                fileEntry,
                fileEntries = [];

                if (upload.options.async.batch === true) {
                    name = $.map(files, function (file) { return file.name; })
                       .join(", ");

                    fileEntry = upload._enqueueFile(name, { fileNames: files });
                    fileEntry.data("files", files);

                    fileEntries.push(fileEntry);
                } else {
                    for (i = 0; i < filesLength; i++) {
                        currentFile = files[i];
                        name = currentFile.name;

                        fileEntry = upload._enqueueFile(name, { fileNames: [currentFile] });
                        fileEntry.data("files", [currentFile]);

                        fileEntries.push(fileEntry);
                    }
                }

                return fileEntries;
            },

            performUpload: function (fileEntry) {
                var upload = this.upload,
                formData = this.createFormData(),
                xhr = this.createXHR(),
                e = {
                    files: fileEntry.data("fileNames"),
                    XMLHttpRequest: xhr
                };

                if (!upload.trigger(UPLOAD, e)) {
                    upload._fileAction(fileEntry, CANCEL);
                    upload._hideUploadButton();
                    upload._showHeaderUploadStatus();

                    e.data = $.extend({}, e.data, getAntiForgeryTokens());
                    for (var key in e.data) {
                        formData.append(key, e.data[key]);
                    }

                    this.populateFormData(formData, fileEntry.data("files"));

                    upload._fileState(fileEntry, "uploading");
                    $(fileEntry).removeClass("k-file-error").addClass("k-file-progress");

                    this.postFormData(upload.options.async.saveUrl, formData, fileEntry, xhr);
                } else {
                    this.removeFileEntry(fileEntry);
                }
            },

            onSaveSelected: function () {
                var module = this;

                $(".k-file", this.element).each(function () {
                    var fileEntry = $(this),
                    started = isFileUploadStarted(fileEntry);

                    if (!started) {
                        module.performUpload(fileEntry);
                    }
                });
            },

            onCancel: function (e) {
                var fileEntry = getFileEntry(e);
                this.stopUploadRequest(fileEntry);
                this.removeFileEntry(fileEntry);
            },

            onRetry: function (e) {
                var fileEntry = getFileEntry(e);
                this.performUpload(fileEntry);
            },

            onRemove: function (e, data) {
                var fileEntry = getFileEntry(e);

                if (fileEntry.hasClass("k-file-success")) {
                    removeUploadedFile(fileEntry, this.upload, data);
                } else {
                    this.removeFileEntry(fileEntry);
                }
            },

            createXHR: function () {
                return new XMLHttpRequest();
            },

            postFormData: function (url, data, fileEntry, xhr) {
                var module = this;

                fileEntry.data("request", xhr);

                xhr.addEventListener("load", function (e) {
                    module.onRequestSuccess.call(module, e, fileEntry);
                }, false);

                xhr.addEventListener(ERROR, function (e) {
                    module.onRequestError.call(module, e, fileEntry);
                }, false);

                xhr.upload.addEventListener("progress", function (e) {
                    module.onRequestProgress.call(module, e, fileEntry);
                }, false);

                xhr.open("POST", url, true);
                xhr.withCredentials = this.upload.options.async.withCredentials;
                xhr.send(data);
            },

            createFormData: function () {
                return new FormData();
            },

            populateFormData: function (data, files) {
                var upload = this.upload,
                i,
                length = files.length;

                for (i = 0; i < length; i++) {
                    data.append(
                    upload.options.async.saveField || upload.name,
                    files[i].rawFile
                );
                }

                return data;
            },

            onRequestSuccess: function (e, fileEntry) {
                var xhr = e.target,
                module = this;

                function raiseError() {
                    module.upload._onUploadError({ target: $(fileEntry, module.upload.wrapper) }, xhr);
                }

                if (xhr.status >= 200 && xhr.status <= 299) {
                    tryParseJSON(xhr.responseText,
                    function (jsonResult) {
                        module.upload._onFileProgress({ target: $(fileEntry, module.upload.wrapper) }, 100);
                        module.upload._onUploadSuccess({ target: $(fileEntry, module.upload.wrapper) }, jsonResult, xhr);
                        module.cleanupFileEntry(fileEntry);
                    },
                    raiseError
                );
                } else {
                    raiseError();
                }
            },

            onRequestError: function (e, fileEntry) {
                var xhr = e.target;
                this.upload._onUploadError({ target: $(fileEntry, this.upload.wrapper) }, xhr);
            },

            cleanupFileEntry: function (fileEntry) {
                var relatedInput = fileEntry.data("relatedInput"),
                uploadComplete = true;

                if (relatedInput) {
                    $.each(relatedInput.data("relatedFileEntries") || [], function () {
                        // Exclude removed file entries and self
                        if (this.parent().length > 0 && this[0] != fileEntry[0]) {
                            uploadComplete = uploadComplete && this.hasClass("k-file-success");
                        }
                    });

                    if (uploadComplete) {
                        relatedInput.remove();
                    }
                }
            },

            removeFileEntry: function (fileEntry) {
                this.cleanupFileEntry(fileEntry);
                this.upload._removeFileEntry(fileEntry);
            },

            onRequestProgress: function (e, fileEntry) {
                var percentComplete = Math.round(e.loaded * 100 / e.total);
                this.upload._onFileProgress({ target: $(fileEntry, this.upload.wrapper) }, percentComplete);
            },

            stopUploadRequest: function (fileEntry) {
                fileEntry.data("request").abort();
            }
        };

        // Helper functions
        function getFileName(input) {
            return $.map(inputFiles(input), function (file) {
                return file.name;
            }).join(", ");
        }

        function inputFiles($input) {
            var input = $input[0];

            if (input.files) {
                return getAllFileInfo(input.files);
            } else {
                return [{
                    name: stripPath(input.value),
                    extension: getFileExtension(input.value),
                    size: null
                }];
            }
        }

        function getAllFileInfo(rawFiles) {
            return $.map(rawFiles, function (file) {
                return getFileInfo(file);
            });
        }

        function getFileInfo(rawFile) {
            // Older Firefox versions (before 3.6) use fileName and fileSize
            var fileName = rawFile.name || rawFile.fileName;
            return {
                name: kendo.htmlEncode(fileName),
                extension: getFileExtension(fileName),
                size: rawFile.size || rawFile.fileSize,
                rawFile: rawFile
            };
        }

        function getFileExtension(fileName) {
            var matches = fileName.match(rFileExtension);
            return matches ? matches[0] : "";
        }

        function stripPath(name) {
            var slashIndex = name.lastIndexOf("\\");
            return (slashIndex != -1) ? name.substr(slashIndex + 1) : name;
        }

        function assignGuidToFiles(files, unique) {
            var uid = kendo.guid();

            return $.map(files, function (file) {
                file.uid = unique ? kendo.guid() : uid;

                return file;
            });
        }

        function removeUploadedFile(fileEntry, upload, data) {
            if (!upload._supportsRemove()) {
                return;
            }

            var files = fileEntry.data("fileNames");
            var fileNames = $.map(files, function (file) { return file.name; });

            upload._submitRemove(fileNames, data,
            function onSuccess(data, textStatus, xhr) {
                upload._removeFileEntry(fileEntry);

                upload.trigger(SUCCESS, {
                    operation: "remove",
                    files: files,
                    response: data,
                    XMLHttpRequest: xhr
                });
            },

            function onError(xhr) {
                upload.trigger(ERROR, {
                    operation: "remove",
                    files: files,
                    XMLHttpRequest: xhr
                });

                logToConsole("Server response: " + xhr.responseText);
            }
        );
        }

        function tryParseJSON(input, onSuccess, onError) {
            var success = false,
            json = "";

            try {
                json = $.parseJSON(normalizeJSON(input));
                success = true;
            } catch (e) {
                onError();
            }

            if (success) {
                onSuccess(json);
            }
        }

        function normalizeJSON(input) {
            if (typeof input === "undefined" || input === "") {
                input = "{}";
            }

            return input;
        }

        function stopEvent(e) {
            e.stopPropagation(); e.preventDefault();
        }

        function bindDragEventWrappers(element, namespace, onDragEnter, onDragLeave) {
            var hideInterval, lastDrag;

            element
            .on("dragenter" + namespace, function () {
                onDragEnter();
                lastDrag = new Date();

                if (!hideInterval) {
                    hideInterval = setInterval(function () {
                        var sinceLastDrag = new Date() - lastDrag;
                        if (sinceLastDrag > 100) {
                            onDragLeave();

                            clearInterval(hideInterval);
                            hideInterval = null;
                        }
                    }, 100);
                }
            })
            .on("dragover" + namespace, function () {
                lastDrag = new Date();
            });
        }

        function isFileUploadStarted(fileEntry) {
            return fileEntry.is(".k-file-progress, .k-file-success, .k-file-error");
        }

        function getFileEntry(e) {
            return $(e.target).closest(".k-file");
        }

        function getAntiForgeryTokens() {
            var tokens = {},
            csrf_token = $("meta[name=csrf-token]").attr("content"),
            csrf_param = $("meta[name=csrf-param]").attr("content");

            $("input[name^='__RequestVerificationToken']").each(function () {
                tokens[this.name] = this.value;
            });

            if (csrf_param !== undefined && csrf_token !== undefined) {
                tokens[csrf_param] = csrf_token;
            }

            return tokens;
        }
        kendo.ui.plugin(Upload);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        isPlainObject = $.isPlainObject,
        proxy = $.proxy,
        extend = $.extend,
        placeholderSupported = kendo.support.placeholder,
        browser = kendo.support.browser,
        isFunction = kendo.isFunction,
        trimSlashesRegExp = /(^\/|\/$)/g,
        CHANGE = "change",
        APPLY = "apply",
        ERROR = "error",
        CLICK = "click",
        NS = ".kendoFileBrowser",
        BREADCRUBMSNS = ".kendoBreadcrumbs",
        SEARCHBOXNS = ".kendoSearchBox",
        NAMEFIELD = "name",
        SIZEFIELD = "size",
        TYPEFIELD = "type",
        DEFAULTSORTORDER = { field: TYPEFIELD, dir: "asc" },
        EMPTYTILE = kendo.template('<li class="k-tile-empty"><strong>${text}</strong></li>'),
        TOOLBARTMPL = '<div class="k-widget k-filebrowser-toolbar k-header k-floatwrap">' +
                            '<div class="k-toolbar-wrap">' +
                                '# if (showUpload) { # ' +
                                    '<div class="k-widget k-upload"><div class="k-button k-button-icontext k-upload-button">' +
                                        '<span class="k-icon k-add"></span>#=messages.uploadFile#<input type="file" name="file" /></div></div>' +
                                '# } #' +

                                '# if (showCreate) { #' +
                                     '<button type="button" class="k-button k-button-icon"><span class="k-icon k-addfolder" /></button>' +
                                '# } #' +

                                '# if (showDelete) { #' +
                                    '<button type="button" class="k-button k-button-icon k-state-disabled"><span class="k-icon k-delete" /></button>&nbsp;' +
                                '# } #' +
                            '</div>' +
                            '<div class="k-tiles-arrange">' +
                                '<label>#=messages.orderBy#: <select /></label></a>' +
                            '</div>' +
                        '</div>';

        extend(true, kendo.data, {
            schemas: {
                "filebrowser": {
                    data: function (data) {
                        return data.items || data || [];
                    },
                    model: {
                        id: "name",
                        fields: {
                            name: "name",
                            size: "size",
                            type: "type"
                        }
                    }
                }
            }
        });

        extend(true, kendo.data, {
            transports: {
                "filebrowser": kendo.data.RemoteTransport.extend({
                    init: function (options) {
                        kendo.data.RemoteTransport.fn.init.call(this, $.extend(true, {}, this.options, options));
                    },
                    _call: function (type, options) {
                        options.data = $.extend({}, options.data, { path: this.options.path() });

                        if (isFunction(this.options[type])) {
                            this.options[type].call(this, options);
                        } else {
                            kendo.data.RemoteTransport.fn[type].call(this, options);
                        }
                    },
                    read: function (options) {
                        this._call("read", options);
                    },
                    create: function (options) {
                        this._call("create", options);
                    },
                    destroy: function (options) {
                        this._call("destroy", options);
                    },
                    update: function () {
                        //updates are handled by the upload
                    },
                    options: {
                        read: {
                            type: "POST"
                        },
                        update: {
                            type: "POST"
                        },
                        create: {
                            type: "POST"
                        },
                        destroy: {
                            type: "POST"
                        }
                    }
                })
            }
        });

        function bindDragEventWrappers(element, onDragEnter, onDragLeave) {
            var hideInterval, lastDrag;

            element
            .on("dragenter" + NS, function () {
                onDragEnter();
                lastDrag = new Date();

                if (!hideInterval) {
                    hideInterval = setInterval(function () {
                        var sinceLastDrag = new Date() - lastDrag;
                        if (sinceLastDrag > 100) {
                            onDragLeave();

                            clearInterval(hideInterval);
                            hideInterval = null;
                        }
                    }, 100);
                }
            })
            .on("dragover" + NS, function () {
                lastDrag = new Date();
            });
        }

        var offsetTop;
        if (browser.msie && browser.version < 8) {
            offsetTop = function (element) {
                return element.offsetTop;
            };
        } else {
            offsetTop = function (element) {
                return element.offsetTop - $(element).height();
            };
        }

        function concatPaths(path, name) {
            if (path === undefined || !path.match(/\/$/)) {
                path = (path || "") + "/";
            }
            return path + name;
        }

        function sizeFormatter(value) {
            if (!value) {
                return "";
            }

            var suffix = " bytes";

            if (value >= 1073741824) {
                suffix = " GB";
                value /= 1073741824;
            } else if (value >= 1048576) {
                suffix = " MB";
                value /= 1048576;
            } else if (value >= 1024) {
                suffix = " KB";
                value /= 1024;
            }

            return Math.round(value * 100) / 100 + suffix;
        }

        function fieldName(fields, name) {
            var descriptor = fields[name];

            if (isPlainObject(descriptor)) {
                return descriptor.from || descriptor.field || name;
            }
            return descriptor;
        }

        var FileBrowser = Widget.extend({
            init: function (element, options) {
                var that = this;

                options = options || {};

                Widget.fn.init.call(that, element, options);

                that.element.addClass("k-filebrowser");

                that.element
                .on(CLICK + NS, ".k-filebrowser-toolbar button:not(.k-state-disabled):has(.k-delete)", proxy(that._deleteClick, that))
                .on(CLICK + NS, ".k-filebrowser-toolbar button:not(.k-state-disabled):has(.k-addfolder)", proxy(that._addClick, that))
                .on("keydown" + NS, "li.k-state-selected input", proxy(that._directoryKeyDown, that))
                .on("blur" + NS, "li.k-state-selected input", proxy(that._directoryBlur, that));

                that._dataSource();

                that.refresh();

                that.path(that.options.path);
            },

            options: {
                name: "FileBrowser",
                messages: {
                    uploadFile: "Upload",
                    orderBy: "Arrange by",
                    orderByName: "Name",
                    orderBySize: "Size",
                    directoryNotFound: "A directory with this name was not found.",
                    emptyFolder: "Empty Folder",
                    deleteFile: 'Are you sure you want to delete "{0}"?',
                    invalidFileType: "The selected file \"{0}\" is not valid. Supported file types are {1}.",
                    overwriteFile: "A file with name \"{0}\" already exists in the current directory. Do you want to overwrite it?",
                    dropFilesHere: "drop file here to upload",
                    search: "Search"
                },
                transport: {},
                path: "/",
                fileTypes: "*.*"
            },

            events: [ERROR, CHANGE, APPLY],

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.dataSource
                .unbind(ERROR, that._errorHandler);

                that.element
                .add(that.list)
                .add(that.toolbar)
                .off(NS);

                kendo.destroy(that.element);
            },

            value: function () {
                var that = this,
                selected = that._selectedItem(),
                path,
                fileUrl = that.options.transport.fileUrl;

                if (selected && selected.get(TYPEFIELD) === "f") {
                    path = concatPaths(that.path(), selected.get(NAMEFIELD)).replace(trimSlashesRegExp, "");
                    if (fileUrl) {
                        path = isFunction(fileUrl) ? fileUrl(path) : kendo.format(fileUrl, encodeURIComponent(path));
                    }
                    return path;
                }
            },

            _selectedItem: function () {
                var listView = this.listView,
                selected = listView.select();

                if (selected.length) {
                    return this.dataSource.getByUid(selected.attr(kendo.attr("uid")));
                }
            },

            _toolbar: function () {
                var that = this,
                template = kendo.template(TOOLBARTMPL),
                messages = that.options.messages,
                arrangeBy = [
                    { text: messages.orderByName, value: "name" },
                    { text: messages.orderBySize, value: "size" }
                ];

                that.toolbar = $(template({
                    messages: messages,
                    showUpload: that.options.transport.uploadUrl,
                    showCreate: that.options.transport.create,
                    showDelete: that.options.transport.destroy
                }))
                .appendTo(that.element)
                .find(".k-upload input")
                .kendoUpload({
                    multiple: false,
                    localization: {
                        dropFilesHere: messages.dropFilesHere
                    },
                    async: {
                        saveUrl: that.options.transport.uploadUrl,
                        autoUpload: true
                    },
                    upload: proxy(that._fileUpload, that),
                    error: function (e) {
                        that._error({ xhr: e.XMLHttpRequest, status: "error" });
                    }
                }).end();

                that.upload = that.toolbar
                .find(".k-upload input")
                .data("kendoUpload");

                that.arrangeBy = that.toolbar.find(".k-tiles-arrange select")
                .kendoDropDownList({
                    dataSource: arrangeBy,
                    dataTextField: "text",
                    dataValueField: "value",
                    change: function () {
                        that.orderBy(this.value());
                    }
                })
                .data("kendoDropDownList");

                that._attachDropzoneEvents();
            },

            _attachDropzoneEvents: function () {
                var that = this;

                if (that.options.transport.uploadUrl) {
                    bindDragEventWrappers($(document.documentElement),
                    $.proxy(that._dropEnter, that),
                    $.proxy(that._dropLeave, that)
                );
                    that._scrollHandler = proxy(that._positionDropzone, that);
                }
            },

            _dropEnter: function () {
                this._positionDropzone();
                $(document).on("scroll" + NS, this._scrollHandler);
            },

            _dropLeave: function () {
                this._removeDropzone();
                $(document).off("scroll" + NS, this._scrollHandler);
            },

            _positionDropzone: function () {
                var that = this,
                element = that.element,
                offset = element.offset();

                that.toolbar.find(".k-dropzone")
                .addClass("k-filebrowser-dropzone")
                .offset(offset)
                .css({
                    width: element[0].clientWidth,
                    height: element[0].clientHeight,
                    lineHeight: element[0].clientHeight + "px"
                });
            },

            _removeDropzone: function () {
                this.toolbar.find(".k-dropzone")
                .removeClass("k-filebrowser-dropzone")
                .css({ width: "", height: "", lineHeight: "", top: "", left: "" });
            },

            _deleteClick: function () {
                var that = this,
                item = that.listView.select(),
                message = kendo.format(that.options.messages.deleteFile, item.find("strong").text());

                if (item.length && that._showMessage(message, "confirm")) {
                    that.listView.remove(item);
                }
            },

            _addClick: function () {
                this.createDirectory();
            },

            _getFieldName: function (name) {
                return fieldName(this.dataSource.reader.model.fields, name);
            },

            _fileUpload: function (e) {
                var that = this,
                options = that.options,
                fileTypes = options.fileTypes,
                filterRegExp = new RegExp(("(" + fileTypes.split(",").join(")|(") + ")").replace(/\*\./g, ".*."), "i"),
                fileName = e.files[0].name,
                fileNameField = NAMEFIELD,
                sizeField = SIZEFIELD,
                model;

                if (filterRegExp.test(fileName)) {
                    e.data = { path: that.path() };

                    model = that._createFile(fileName);

                    if (!model) {
                        e.preventDefault();
                    } else {
                        that.upload.one("success", function (e) {
                            model.set(fileNameField, e.response[that._getFieldName(fileNameField)]);
                            model.set(sizeField, e.response[that._getFieldName(sizeField)]);
                            that._tiles = that.listView.items().filter("[" + kendo.attr("type") + "=f]");
                        });
                    }
                } else {
                    e.preventDefault();
                    that._showMessage(kendo.format(options.messages.invalidFileType, fileName, fileTypes));
                }
            },

            _findFile: function (name) {
                var data = this.dataSource.data(),
                idx,
                result,
                typeField = TYPEFIELD,
                nameField = NAMEFIELD,
                length;

                name = name.toLowerCase();

                for (idx = 0, length = data.length; idx < length; idx++) {
                    if (data[idx].get(typeField) === "f" &&
                    data[idx].get(nameField).toLowerCase() === name) {

                        result = data[idx];
                        break;
                    }
                }
                return result;
            },

            _createFile: function (fileName) {
                var that = this,
                idx,
                length,
                index = 0,
                model = {},
                typeField = TYPEFIELD,
                view = that.dataSource.view(),
                file = that._findFile(fileName);

                if (file && !that._showMessage(kendo.format(that.options.messages.overwriteFile, fileName), "confirm")) {
                    return null;
                }

                if (file) {
                    return file;
                }

                for (idx = 0, length = view.length; idx < length; idx++) {
                    if (view[idx].get(typeField) === "f") {
                        index = idx;
                        break;
                    }
                }

                model[typeField] = "f";
                model[NAMEFIELD] = fileName;
                model[SIZEFIELD] = 0;

                return that.dataSource.insert(++index, model);
            },

            createDirectory: function () {
                var that = this,
                idx,
                length,
                lastDirectoryIdx = 0,
                typeField = TYPEFIELD,
                nameField = NAMEFIELD,
                view = that.dataSource.data(),
                name = that._nameDirectory(),
                model = new that.dataSource.reader.model();

                for (idx = 0, length = view.length; idx < length; idx++) {
                    if (view[idx].get(typeField) === "d") {
                        lastDirectoryIdx = idx;
                    }
                }

                model.set(typeField, "d");
                model.set(nameField, name);

                that.listView.one("dataBound", function () {
                    var selected = that.listView.items()
                    .filter("[" + kendo.attr("uid") + "=" + model.uid + "]"),
                    input = selected.find("input");

                    if (selected.length) {
                        this.edit(selected);
                    }

                    this.element.scrollTop(selected.attr("offsetTop") - this.element[0].offsetHeight);

                    setTimeout(function () {
                        input.select();
                    });
                })
            .one("save", function (e) {
                var value = e.model.get(nameField);

                if (!value) {
                    e.model.set(nameField, name);
                } else {
                    e.model.set(nameField, that._nameExists(value, model.uid) ? that._nameDirectory() : value);
                }
            });

                that.dataSource.insert(++lastDirectoryIdx, model);
            },

            _directoryKeyDown: function (e) {
                if (e.keyCode == 13) {
                    e.currentTarget.blur();
                }
            },

            _directoryBlur: function () {
                this.listView.save();
            },

            _nameExists: function (name, uid) {
                var data = this.dataSource.data(),
                typeField = TYPEFIELD,
                nameField = NAMEFIELD,
                idx,
                length;

                for (idx = 0, length = data.length; idx < length; idx++) {
                    if (data[idx].get(typeField) === "d" &&
                    data[idx].get(nameField).toLowerCase() === name.toLowerCase() &&
                    data[idx].uid !== uid) {
                        return true;
                    }
                }
                return false;
            },

            _nameDirectory: function () {
                var name = "New folder",
                data = this.dataSource.data(),
                directoryNames = [],
                typeField = TYPEFIELD,
                nameField = NAMEFIELD,
                candidate,
                idx,
                length;

                for (idx = 0, length = data.length; idx < length; idx++) {
                    if (data[idx].get(typeField) === "d" && data[idx].get(nameField).toLowerCase().indexOf(name.toLowerCase()) > -1) {
                        directoryNames.push(data[idx].get(nameField));
                    }
                }

                if ($.inArray(name, directoryNames) > -1) {
                    idx = 2;

                    do {
                        candidate = name + " (" + idx + ")";
                        idx++;
                    } while ($.inArray(candidate, directoryNames) > -1);

                    name = candidate;
                }

                return name;
            },

            orderBy: function (field) {
                this.dataSource.sort([
                { field: TYPEFIELD, dir: "asc" },
                { field: field, dir: "asc" }
            ]);
            },

            search: function (name) {
                this.dataSource.filter({
                    field: NAMEFIELD,
                    operator: "contains",
                    value: name
                });
            },

            _content: function () {
                var that = this;

                that.list = $('<ul class="k-reset k-floats k-tiles" />')
                .appendTo(that.element)
                .on("dblclick" + NS, "li", proxy(that._dblClick, that));

                that.listView = new kendo.ui.ListView(that.list, {
                    dataSource: that.dataSource,
                    template: that._itemTmpl(),
                    editTemplate: that._editTmpl(),
                    selectable: true,
                    autoBind: false,
                    dataBinding: function (e) {
                        that.toolbar.find(".k-delete").parent().addClass("k-state-disabled");

                        if (e.action === "remove" || e.action === "sync") {
                            e.preventDefault();
                        }
                    },
                    dataBound: function () {
                        if (that.dataSource.view().length) {
                            that._tiles = this.items().filter("[" + kendo.attr("type") + "=f]");
                        } else {
                            this.wrapper.append(EMPTYTILE({ text: that.options.messages.emptyFolder }));
                        }
                    },
                    change: proxy(that._listViewChange, that)
                });
            },

            _dblClick: function (e) {
                var that = this,
                li = $(e.currentTarget);

                if (li.hasClass("k-edit-item")) {
                    that._directoryBlur();
                }

                if (li.filter("[" + kendo.attr("type") + "=d]").length) {
                    var folder = that.dataSource.getByUid(li.attr(kendo.attr("uid")));
                    if (folder) {
                        that.path(concatPaths(that.path(), folder.get(NAMEFIELD)));
                        that.breadcrumbs.value(that.path());
                    }
                } else if (li.filter("[" + kendo.attr("type") + "=f]").length) {
                    that.trigger(APPLY);
                }
            },

            _listViewChange: function () {
                var selected = this._selectedItem();

                if (selected) {
                    this.toolbar.find(".k-delete").parent().removeClass("k-state-disabled");

                    if (selected.get(TYPEFIELD) === "f") {
                        this.trigger(CHANGE);
                    }
                }
            },

            _dataSource: function () {
                var that = this,
                options = that.options,
                transport = options.transport,
                typeSortOrder = extend({}, DEFAULTSORTORDER),
                nameSortOrder = { field: NAMEFIELD, dir: "asc" },
                schema,
                dataSource = {
                    type: transport.type || "filebrowser",
                    sort: [typeSortOrder, nameSortOrder]
                };

                if (isPlainObject(transport)) {
                    transport.path = proxy(that.path, that);
                    dataSource.transport = transport;
                }

                if (isPlainObject(options.schema)) {
                    dataSource.schema = options.schema;
                } else if (transport.type && isPlainObject(kendo.data.schemas[transport.type])) {
                    schema = kendo.data.schemas[transport.type];
                }

                if (that.dataSource && that._errorHandler) {
                    that.dataSource.unbind(ERROR, that._errorHandler);
                } else {
                    that._errorHandler = proxy(that._error, that);
                }

                that.dataSource = kendo.data.DataSource.create(dataSource)
                .bind(ERROR, that._errorHandler);
            },

            _navigation: function () {
                var that = this,
                navigation = $('<div class="k-floatwrap"><input/><input/></div>')
                    .appendTo(this.element);

                that.breadcrumbs = navigation.find("input:first")
                    .kendoBreadcrumbs({
                        value: that.options.path,
                        change: function () {
                            that.path(this.value());
                        }
                    }).data("kendoBreadcrumbs");

                that.searchBox = navigation.parent().find("input:last")
                    .kendoSearchBox({
                        label: that.options.messages.search,
                        change: function () {
                            that.search(this.value());
                        }
                    }).data("kendoSearchBox");
            },

            _error: function (e) {
                var that = this,
                status;

                if (!that.trigger(ERROR, e)) {
                    status = e.xhr.status;

                    if (e.status == 'error') {
                        if (status == '404') {
                            that._showMessage(that.options.messages.directoryNotFound);
                        } else if (status != '0') {
                            that._showMessage('Error! The requested URL returned ' + status + ' - ' + e.xhr.statusText);
                        }
                    } else if (status == 'timeout') {
                        that._showMessage('Error! Server timeout.');
                    }
                }
            },

            _showMessage: function (message, type) {
                return window[type || "alert"](message);
            },

            refresh: function () {
                var that = this;
                that._navigation();
                that._toolbar();
                that._content();
            },

            _editTmpl: function () {
                var html = '<li class="k-tile k-state-selected" ' + kendo.attr("uid") + '="#=uid#" ';

                html += kendo.attr("type") + '="${' + TYPEFIELD + '}">';
                html += '#if(' + TYPEFIELD + ' == "d") { #';
                html += '<div class="k-thumb"><span class="k-icon k-folder"></span></div>';
                html += "#}else{#";
                html += '<div class="k-thumb"><span class="k-icon k-loading"></span></div>';
                html += "#}#";
                html += '#if(' + TYPEFIELD + ' == "d") { #';
                html += '<input class="k-input" ' + kendo.attr("bind") + '="value:' + NAMEFIELD + '"/>';
                html += "#}#";
                html += '</li>';

                return proxy(kendo.template(html), { sizeFormatter: sizeFormatter });
            },

            _itemTmpl: function () {
                var that = this,
                html = '<li class="k-tile" ' + kendo.attr("uid") + '="#=uid#" ';

                html += kendo.attr("type") + '="${' + TYPEFIELD + '}">';
                html += '#if(' + TYPEFIELD + ' == "d") { #';
                html += '<div class="k-thumb"><span class="k-icon k-folder"></span></div>';
                html += "#}else{#";
                html += '<div class="k-thumb"><span class="k-icon k-file"></span></div>';
                html += "#}#";
                html += '<strong>${' + NAMEFIELD + '}</strong>';
                html += '#if(' + TYPEFIELD + ' == "f") { # <span class="k-filesize">${this.sizeFormatter(' + SIZEFIELD + ')}</span> #}#';
                html += '</li>';

                return proxy(kendo.template(html), { sizeFormatter: sizeFormatter });
            },

            path: function (value) {
                var that = this,
                path = that._path || "";

                if (value !== undefined) {
                    that._path = value.replace(trimSlashesRegExp, "") + "/";
                    that.dataSource.read({ path: that._path });
                    return;
                }

                if (path) {
                    path = path.replace(trimSlashesRegExp, "");
                }

                return path === "/" || path === "" ? "" : (path + "/");
            }
        });

        var SearchBox = Widget.extend({
            init: function (element, options) {
                var that = this;

                options = options || {};

                Widget.fn.init.call(that, element, options);

                if (placeholderSupported) {
                    that.element.attr("placeholder", that.options.label);
                }

                that._wrapper();

                that.element
                .on("keydown" + SEARCHBOXNS, proxy(that._keydown, that))
                .on("change" + SEARCHBOXNS, proxy(that._updateValue, that));

                that.wrapper
                .on(CLICK + SEARCHBOXNS, "a", proxy(that._click, that));

                if (!placeholderSupported) {
                    that.element.on("focus" + SEARCHBOXNS, proxy(that._focus, that))
                    .on("blur" + SEARCHBOXNS, proxy(that._blur, that));
                }
            },

            options: {
                name: "SearchBox",
                label: "Search",
                value: ""
            },

            events: [CHANGE],

            destroy: function () {
                var that = this;

                that.wrapper
                .add(that.element)
                .add(that.label)
                .off(SEARCHBOXNS);

                Widget.fn.destroy.call(that);
            },

            _keydown: function (e) {
                if (e.keyCode === 13) {
                    this._updateValue();
                }
            },

            _click: function (e) {
                e.preventDefault();
                this._updateValue();
            },

            _updateValue: function () {
                var that = this,
                value = that.element.val();

                if (value !== that.value()) {
                    that.value(value);

                    that.trigger(CHANGE);
                }
            },

            _blur: function () {
                this._updateValue();
                this._toggleLabel();
            },

            _toggleLabel: function () {
                if (!placeholderSupported) {
                    this.label.toggle(!this.element.val());
                }
            },

            _focus: function () {
                this.label.hide();
            },

            _wrapper: function () {
                var element = this.element,
                wrapper = element.parents(".k-search-wrap");

                element[0].style.width = "";
                element.addClass("k-input");

                if (!wrapper.length) {
                    wrapper = element.wrap($('<div class="k-widget k-search-wrap k-textbox"/>')).parent();
                    if (!placeholderSupported) {
                        $('<label style="display:block">' + this.options.label + '</label>').insertBefore(element);
                    }
                    $('<a href="#" class="k-icon k-i-search k-search"/>').appendTo(wrapper);
                }

                this.wrapper = wrapper;
                this.label = wrapper.find(">label");
            },

            value: function (value) {
                var that = this;

                if (value !== undefined) {
                    that.options.value = value;
                    that.element.val(value);
                    that._toggleLabel();
                    return;
                }
                return that.options.value;
            }
        });

        var Breadcrumbs = Widget.extend({
            init: function (element, options) {
                var that = this;

                options = options || {};

                Widget.fn.init.call(that, element, options);

                that._wrapper();

                that.wrapper
                .on("focus" + BREADCRUBMSNS, "input", proxy(that._focus, that))
                .on("blur" + BREADCRUBMSNS, "input", proxy(that._blur, that))
                .on("keydown" + BREADCRUBMSNS, "input", proxy(that._keydown, that))
                .on(CLICK + BREADCRUBMSNS, "a.k-i-arrow-n:first", proxy(that._rootClick, that))
                .on(CLICK + BREADCRUBMSNS, "a:not(.k-i-arrow-n)", proxy(that._click, that));

                that.value(that.options.value);
            },

            options: {
                name: "Breadcrumbs",
                gap: 50
            },

            events: [CHANGE],

            destroy: function () {
                var that = this;

                Widget.fn.destroy.call(that);

                that.wrapper
                .add(that.wrapper.find("input"))
                .add(that.wrapper.find("a"))
                .off(BREADCRUBMSNS);
            },

            _update: function (val) {
                val = (val || "").charAt(0) === "/" ? val : ("/" + (val || ""));

                if (val !== this.value()) {
                    this.value(val);
                    this.trigger(CHANGE);
                }
            },

            _click: function (e) {
                e.preventDefault();
                this._update(this._path($(e.target).prevAll("a:not(.k-i-arrow-n)").addBack()));
            },

            _rootClick: function (e) {
                e.preventDefault();
                this._update("");
            },

            _focus: function () {
                var that = this,
                element = that.element;

                that.overlay.hide();
                that.element.val(that.value());

                setTimeout(function () {
                    element.select();
                });
            },

            _blur: function () {
                if (this.overlay.is(":visible")) {
                    return;
                }

                var that = this,
                element = that.element,
                val = element.val().replace(/\/{2,}/g, "/");

                that.overlay.show();
                element.val("");
                that._update(val);
            },

            _keydown: function (e) {
                var that = this;
                if (e.keyCode === 13) {
                    that._blur();

                    setTimeout(function () {
                        that.overlay.find("a:first").focus();
                    });
                }
            },

            _wrapper: function () {
                var element = this.element,
                wrapper = element.parents(".k-breadcrumbs"),
                overlay;

                element[0].style.width = "";
                element.addClass("k-input");

                if (!wrapper.length) {
                    wrapper = element.wrap($('<div class="k-widget k-breadcrumbs k-textbox"/>')).parent();
                }

                overlay = wrapper.find(".k-breadcrumbs-wrap");
                if (!overlay.length) {
                    overlay = $('<div class="k-breadcrumbs-wrap"/>').appendTo(wrapper);
                }
                this.wrapper = wrapper;
                this.overlay = overlay;
            },

            refresh: function () {
                var html = "",
                value = this.value(),
                segments,
                segment,
                idx,
                length;

                if (value === undefined || !value.match(/^\//)) {
                    value = "/" + (value || "");
                }

                segments = value.split("/");

                for (idx = 0, length = segments.length; idx < length; idx++) {
                    segment = segments[idx];
                    if (segment) {
                        if (!html) {
                            html += '<a href="#" class="k-icon k-i-arrow-n">root</a>';
                        }
                        html += '<a class="k-link" href="#">' + segments[idx] + '</a>';
                        html += '<span class="k-icon k-i-arrow-e">&gt;</span>';
                    }
                }
                this.overlay.empty().append($(html));

                this._adjustSectionWidth();
            },

            _adjustSectionWidth: function () {
                var that = this,
                wrapper = that.wrapper,
                width = wrapper.width() - that.options.gap,
                links = that.overlay.find("a"),
                a;

                links.each(function (index) {
                    a = $(this);

                    if (a.parent().width() > width) {
                        if (index == links.length - 1) {
                            a.width(width);
                        } else {
                            a.prev().addBack().hide();
                        }
                    }
                });
            },

            value: function (val) {
                if (val !== undefined) {
                    this._value = val.replace(/\/{2,}/g, "/");
                    this.refresh();
                    return;
                }
                return this._value;
            },

            _path: function (trail) {
                return "/" + $.map(trail, function (b) {
                    return $(b).text();
                }).join("/");
            }
        });

        kendo.ui.plugin(FileBrowser);
        kendo.ui.plugin(Breadcrumbs);
        kendo.ui.plugin(SearchBox);
    })(window.kendo.jQuery);





    (function ($, undefined) {
        var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        FileBrowser = kendo.ui.FileBrowser,
        isPlainObject = $.isPlainObject,
        proxy = $.proxy,
        extend = $.extend,
        placeholderSupported = kendo.support.placeholder,
        browser = kendo.support.browser,
        isFunction = kendo.isFunction,
        trimSlashesRegExp = /(^\/|\/$)/g,
        CHANGE = "change",
        APPLY = "apply",
        ERROR = "error",
        CLICK = "click",
        NS = ".kendoImageBrowser",
        NAMEFIELD = "name",
        SIZEFIELD = "size",
        TYPEFIELD = "type",
        DEFAULTSORTORDER = { field: TYPEFIELD, dir: "asc" },
        EMPTYTILE = kendo.template('<li class="k-tile-empty"><strong>${text}</strong></li>'),
        TOOLBARTMPL = '<div class="k-widget k-filebrowser-toolbar k-header k-floatwrap">' +
                            '<div class="k-toolbar-wrap">' +
                                '# if (showUpload) { # ' +
                                    '<div class="k-widget k-upload"><div class="k-button k-button-icontext k-upload-button">' +
                                        '<span class="k-icon k-add"></span>#=messages.uploadFile#<input type="file" name="file" /></div></div>' +
                                '# } #' +

                                '# if (showCreate) { #' +
                                     '<button type="button" class="k-button k-button-icon"><span class="k-icon k-addfolder" /></button>' +
                                '# } #' +

                                '# if (showDelete) { #' +
                                    '<button type="button" class="k-button k-button-icon k-state-disabled"><span class="k-icon k-delete" /></button>&nbsp;' +
                                '# } #' +
                            '</div>' +
                            '<div class="k-tiles-arrange">' +
                                '<label>#=messages.orderBy#: <select /></label></a>' +
                            '</div>' +
                        '</div>';

        extend(true, kendo.data, {
            schemas: {
                "imagebrowser": {
                    data: function (data) {
                        return data.items || data || [];
                    },
                    model: {
                        id: "name",
                        fields: {
                            name: "name",
                            size: "size",
                            type: "type"
                        }
                    }
                }
            }
        });

        extend(true, kendo.data, {
            transports: {
                "imagebrowser": kendo.data.RemoteTransport.extend({
                    init: function (options) {
                        kendo.data.RemoteTransport.fn.init.call(this, $.extend(true, {}, this.options, options));
                    },
                    _call: function (type, options) {
                        options.data = $.extend({}, options.data, { path: this.options.path() });

                        if (isFunction(this.options[type])) {
                            this.options[type].call(this, options);
                        } else {
                            kendo.data.RemoteTransport.fn[type].call(this, options);
                        }
                    },
                    read: function (options) {
                        this._call("read", options);
                    },
                    create: function (options) {
                        this._call("create", options);
                    },
                    destroy: function (options) {
                        this._call("destroy", options);
                    },
                    update: function () {
                        //updates are handled by the upload
                    },
                    options: {
                        read: {
                            type: "POST"
                        },
                        update: {
                            type: "POST"
                        },
                        create: {
                            type: "POST"
                        },
                        destroy: {
                            type: "POST"
                        }
                    }
                })
            }
        });

        function bindDragEventWrappers(element, onDragEnter, onDragLeave) {
            var hideInterval, lastDrag;

            element
            .on("dragenter" + NS, function () {
                onDragEnter();
                lastDrag = new Date();

                if (!hideInterval) {
                    hideInterval = setInterval(function () {
                        var sinceLastDrag = new Date() - lastDrag;
                        if (sinceLastDrag > 100) {
                            onDragLeave();

                            clearInterval(hideInterval);
                            hideInterval = null;
                        }
                    }, 100);
                }
            })
            .on("dragover" + NS, function () {
                lastDrag = new Date();
            });
        }

        var offsetTop;
        if (browser.msie && browser.version < 8) {
            offsetTop = function (element) {
                return element.offsetTop;
            };
        } else {
            offsetTop = function (element) {
                return element.offsetTop - $(element).height();
            };
        }

        function concatPaths(path, name) {
            if (path === undefined || !path.match(/\/$/)) {
                path = (path || "") + "/";
            }
            return path + name;
        }

        function sizeFormatter(value) {
            if (!value) {
                return "";
            }

            var suffix = " bytes";

            if (value >= 1073741824) {
                suffix = " GB";
                value /= 1073741824;
            } else if (value >= 1048576) {
                suffix = " MB";
                value /= 1048576;
            } else if (value >= 1024) {
                suffix = " KB";
                value /= 1024;
            }

            return Math.round(value * 100) / 100 + suffix;
        }

        function fieldName(fields, name) {
            var descriptor = fields[name];

            if (isPlainObject(descriptor)) {
                return descriptor.from || descriptor.field || name;
            }
            return descriptor;
        }

        var ImageBrowser = FileBrowser.extend({
            init: function (element, options) {
                var that = this;

                options = options || {};

                FileBrowser.fn.init.call(that, element, options);

                that.element.addClass("k-imagebrowser");
            },

            options: {
                name: "ImageBrowser",
                fileTypes: "*.png,*.gif,*.jpg,*.jpeg"
            },

            value: function () {
                var that = this,
                selected = that._selectedItem(),
                path,
                imageUrl = that.options.transport.imageUrl;

                if (selected && selected.get(TYPEFIELD) === "f") {
                    path = concatPaths(that.path(), selected.get(NAMEFIELD)).replace(trimSlashesRegExp, "");
                    if (imageUrl) {
                        path = isFunction(imageUrl) ? imageUrl(path) : kendo.format(imageUrl, encodeURIComponent(path));
                    }
                    return path;
                }
            },

            _fileUpload: function (e) {
                var that = this,
                options = that.options,
                fileTypes = options.fileTypes,
                filterRegExp = new RegExp(("(" + fileTypes.split(",").join(")|(") + ")").replace(/\*\./g, ".*."), "i"),
                fileName = e.files[0].name,
                fileNameField = NAMEFIELD,
                sizeField = SIZEFIELD,
                model;

                if (filterRegExp.test(fileName)) {
                    e.data = { path: that.path() };

                    model = that._createFile(fileName);

                    if (!model) {
                        e.preventDefault();
                    } else {
                        model._uploading = true;
                        that.upload.one("success", function (e) {
                            delete model._uploading;
                            model.set(fileNameField, e.response[that._getFieldName(fileNameField)]);
                            model.set(sizeField, e.response[that._getFieldName(sizeField)]);
                            that._tiles = that.listView.items().filter("[" + kendo.attr("type") + "=f]");
                            that._scroll();
                        });
                    }
                } else {
                    e.preventDefault();
                    that._showMessage(kendo.format(options.messages.invalidFileType, fileName, fileTypes));
                }
            },

            _content: function () {
                var that = this;

                that.list = $('<ul class="k-reset k-floats k-tiles" />')
                .appendTo(that.element)
                .on("scroll" + NS, proxy(that._scroll, that))
                .on("dblclick" + NS, "li", proxy(that._dblClick, that));

                that.listView = new kendo.ui.ListView(that.list, {
                    dataSource: that.dataSource,
                    template: that._itemTmpl(),
                    editTemplate: that._editTmpl(),
                    selectable: true,
                    autoBind: false,
                    dataBinding: function (e) {
                        that.toolbar.find(".k-delete").parent().addClass("k-state-disabled");

                        if (e.action === "remove" || e.action === "sync") {
                            e.preventDefault();
                        }
                    },
                    dataBound: function () {
                        if (that.dataSource.view().length) {
                            that._tiles = this.items().filter("[" + kendo.attr("type") + "=f]");
                            that._scroll();
                        } else {
                            this.wrapper.append(EMPTYTILE({ text: that.options.messages.emptyFolder }));
                        }
                    },
                    change: proxy(that._listViewChange, that)
                });
            },

            _dataSource: function () {
                var that = this,
                options = that.options,
                transport = options.transport,
                typeSortOrder = extend({}, DEFAULTSORTORDER),
                nameSortOrder = { field: NAMEFIELD, dir: "asc" },
                schema,
                dataSource = {
                    type: transport.type || "imagebrowser",
                    sort: [typeSortOrder, nameSortOrder]
                };

                if (isPlainObject(transport)) {
                    transport.path = proxy(that.path, that);
                    dataSource.transport = transport;
                }

                if (isPlainObject(options.schema)) {
                    dataSource.schema = options.schema;
                } else if (transport.type && isPlainObject(kendo.data.schemas[transport.type])) {
                    schema = kendo.data.schemas[transport.type];
                }

                if (that.dataSource && that._errorHandler) {
                    that.dataSource.unbind(ERROR, that._errorHandler);
                } else {
                    that._errorHandler = proxy(that._error, that);
                }

                that.dataSource = kendo.data.DataSource.create(dataSource)
                .bind(ERROR, that._errorHandler);
            },

            _loadImage: function (li) {
                var that = this,
                element = $(li),
                dataItem = that.dataSource.getByUid(element.attr(kendo.attr("uid"))),
                name = dataItem.get(NAMEFIELD),
                thumbnailUrl = that.options.transport.thumbnailUrl,
                img = $("<img />", { alt: name }),
                urlJoin = "?";

                if (dataItem._uploading) {
                    return;
                }

                img.hide()
               .on("load" + NS, function () {
                   $(this).prev().remove().end().addClass("k-image").fadeIn();
               });

                element.find(".k-loading").after(img);

                if (isFunction(thumbnailUrl)) {
                    thumbnailUrl = thumbnailUrl(that.path(), encodeURIComponent(name));
                } else {
                    if (thumbnailUrl.indexOf("?") >= 0) {
                        urlJoin = "&";
                    }

                    thumbnailUrl = thumbnailUrl + urlJoin + "path=" + that.path() + encodeURIComponent(name);
                }

                // IE8 will trigger the load event immediately when the src is assigned
                // if the image is loaded from the cache
                img.attr("src", thumbnailUrl);

                li.loaded = true;
            },

            _scroll: function () {
                var that = this;
                if (that.options.transport && that.options.transport.thumbnailUrl) {
                    clearTimeout(that._timeout);

                    that._timeout = setTimeout(function () {
                        var height = that.list.outerHeight(),
                        viewTop = that.list.scrollTop(),
                        viewBottom = viewTop + height;

                        that._tiles.each(function () {
                            var top = offsetTop(this),
                            bottom = top + this.offsetHeight;

                            if ((top >= viewTop && top < viewBottom) || (bottom >= viewTop && bottom < viewBottom)) {
                                that._loadImage(this);
                            }

                            if (top > viewBottom) {
                                return false;
                            }
                        });

                        that._tiles = that._tiles.filter(function () {
                            return !this.loaded;
                        });

                    }, 250);
                }
            },

            _itemTmpl: function () {
                var that = this,
                html = '<li class="k-tile" ' + kendo.attr("uid") + '="#=uid#" ';

                html += kendo.attr("type") + '="${' + TYPEFIELD + '}">';
                html += '#if(' + TYPEFIELD + ' == "d") { #';
                html += '<div class="k-thumb"><span class="k-icon k-folder"></span></div>';
                html += "#}else{#";
                if (that.options.transport && that.options.transport.thumbnailUrl) {
                    html += '<div class="k-thumb"><span class="k-icon k-loading"></span></div>';
                } else {
                    html += '<div class="k-thumb"><span class="k-icon k-file"></span></div>';
                }
                html += "#}#";
                html += '<strong>${' + NAMEFIELD + '}</strong>';
                html += '#if(' + TYPEFIELD + ' == "f") { # <span class="k-filesize">${this.sizeFormatter(' + SIZEFIELD + ')}</span> #}#';
                html += '</li>';

                return proxy(kendo.template(html), { sizeFormatter: sizeFormatter });
            }
        });

        kendo.ui.plugin(ImageBrowser);
    })(window.kendo.jQuery);



    (function ($, undefined) {

        // Imports ================================================================
        var kendo = window.kendo,
        Class = kendo.Class,
        Widget = kendo.ui.Widget,
        os = kendo.support.mobileOS,
        browser = kendo.support.browser,
        extend = $.extend,
        proxy = $.proxy,
        deepExtend = kendo.deepExtend,
        NS = ".kendoEditor",
        keys = kendo.keys;

        // options can be: template (as string), cssClass, title, defaultValue
        var ToolTemplate = Class.extend({
            init: function (options) {
                this.options = options;
            },

            getHtml: function () {
                var options = this.options;
                return kendo.template(options.template, { useWithBlock: false })(options);
            }
        });

        var EditorUtils = {
            editorWrapperTemplate:
            '<table cellspacing="4" cellpadding="0" class="k-widget k-editor k-header" role="presentation"><tbody>' +
                '<tr role="presentation"><td class="k-editor-toolbar-wrap" role="presentation"><ul class="k-editor-toolbar" role="toolbar" /></td></tr>' +
                '<tr><td class="k-editable-area" /></tr>' +
            '</tbody></table>',

            buttonTemplate:
            '<a href="" role="button" class="k-tool"' +
            '#= data.popup ? " data-popup" : "" #' +
            ' unselectable="on" title="#= data.title #"><span unselectable="on" class="k-tool-icon #= data.cssClass #">#= data.title #</span></a>',

            colorPickerTemplate:
            '<div class="k-colorpicker #= data.cssClass #" />',

            comboBoxTemplate:
            '<select title="#= data.title #" class="#= data.cssClass #" />',

            dropDownListTemplate:
            '<span class="k-editor-dropdown"><select title="#= data.title #" class="#= data.cssClass #" /></span>',

            separatorTemplate:
            '<span class="k-separator" />',

            formatByName: function (name, format) {
                for (var i = 0; i < format.length; i++) {
                    if ($.inArray(name, format[i].tags) >= 0) {
                        return format[i];
                    }
                }
            },

            registerTool: function (toolName, tool) {
                var toolOptions = tool.options;

                if (toolOptions && toolOptions.template) {
                    toolOptions.template.options.cssClass = "k-" + toolName;
                }

                if (!tool.name) {
                    tool.options.name = toolName;
                    tool.name = toolName.toLowerCase();
                }

                Editor.defaultTools[toolName] = tool;
            },

            registerFormat: function (formatName, format) {
                Editor.fn.options.formats[formatName] = format;
            }
        };

        var messages = {
            bold: "Bold",
            italic: "Italic",
            underline: "Underline",
            strikethrough: "Strikethrough",
            superscript: "Superscript",
            subscript: "Subscript",
            justifyCenter: "Center text",
            justifyLeft: "Align text left",
            justifyRight: "Align text right",
            justifyFull: "Justify",
            insertUnorderedList: "Insert unordered list",
            insertOrderedList: "Insert ordered list",
            indent: "Indent",
            outdent: "Outdent",
            createLink: "Insert hyperlink",
            unlink: "Remove hyperlink",
            insertImage: "Insert image",
            insertFile: "Insert file",
            insertHtml: "Insert HTML",
            viewHtml: "View HTML",
            fontName: "Select font family",
            fontNameInherit: "(inherited font)",
            fontSize: "Select font size",
            fontSizeInherit: "(inherited size)",
            formatBlock: "Format",
            formatting: "Format",
            foreColor: "Color",
            backColor: "Background color",
            style: "Styles",
            emptyFolder: "Empty Folder",
            uploadFile: "Upload",
            orderBy: "Arrange by:",
            orderBySize: "Size",
            orderByName: "Name",
            invalidFileType: "The selected file \"{0}\" is not valid. Supported file types are {1}.",
            deleteFile: 'Are you sure you want to delete "{0}"?',
            overwriteFile: 'A file with name "{0}" already exists in the current directory. Do you want to overwrite it?',
            directoryNotFound: "A directory with this name was not found.",
            imageWebAddress: "Web address",
            imageAltText: "Alternate text",
            imageWidth: "Width (px)",
            imageHeight: "Height (px)",
            fileWebAddress: "Web address",
            fileTitle: "Title",
            linkWebAddress: "Web address",
            linkText: "Text",
            linkToolTip: "ToolTip",
            linkOpenInNewWindow: "Open link in new window",
            dialogUpdate: "Update",
            dialogInsert: "Insert",
            dialogCancel: "Cancel",
            createTable: "Create table",
            createTableHint: "Create a {0} x {1} table",
            addColumnLeft: "Add column on the left",
            addColumnRight: "Add column on the right",
            addRowAbove: "Add row above",
            addRowBelow: "Add row below",
            deleteRow: "Delete row",
            deleteColumn: "Delete column"
        };

        var supportedBrowser = !os || (os.ios && os.flatVersion >= 500) || (!os.ios && typeof (document.documentElement.contentEditable) != 'undefined');

        var toolGroups = {
            basic: ["bold", "italic", "underline"],
            alignment: ["justifyLeft", "justifyCenter", "justifyRight"],
            lists: ["insertUnorderedList", "insertOrderedList"],
            indenting: ["indent", "outdent"],
            links: ["createLink", "unlink"],
            tables: ["createTable", "addColumnLeft", "addColumnRight", "addRowAbove", "addRowBelow", "deleteRow", "deleteColumn"]
        };

        var Editor = Widget.extend({
            init: function (element, options) {
                var that = this,
                value,
                editorNS = kendo.ui.editor,
                toolbarContainer,
                toolbarOptions,
                type;

                /* suppress initialization in mobile webkit devices (w/o proper contenteditable support) */
                if (!supportedBrowser) {
                    return;
                }

                Widget.fn.init.call(that, element, options);

                that.options = deepExtend({}, that.options, options);

                element = that.element;

                type = editorNS.Dom.name(element[0]);

                element.closest("form").on("submit" + NS, function () {
                    that.update();
                });

                toolbarOptions = extend({}, that.options);
                toolbarOptions.editor = that;

                if (type == "textarea") {
                    that._wrapTextarea();

                    toolbarContainer = that.wrapper.find(".k-editor-toolbar");

                    if (element[0].id) {
                        toolbarContainer.attr("aria-controls", element[0].id);
                    }
                } else {
                    that.element.attr("contenteditable", true).addClass("k-widget k-editor k-editor-inline");

                    toolbarOptions.popup = true;

                    toolbarContainer = $('<ul class="k-editor-toolbar" role="toolbar" />').insertBefore(element);
                }

                that.toolbar = new editorNS.Toolbar(toolbarContainer[0], toolbarOptions);

                that.toolbar.bindTo(that);

                if (type == "textarea") {
                    setTimeout(function () {
                        var heightStyle = that.wrapper[0].style.height;
                        var expectedHeight = parseInt(heightStyle, 10);
                        var actualHeight = that.wrapper.height();
                        if (heightStyle.indexOf("px") > 0 && !isNaN(expectedHeight) && actualHeight > expectedHeight) {
                            that.wrapper.height(expectedHeight - (actualHeight - expectedHeight));
                        }
                    });
                }

                that._initializeContentElement(that);

                that.keyboard = new editorNS.Keyboard([
                new editorNS.TypingHandler(that),
                new editorNS.BackspaceHandler(that),
                new editorNS.SystemHandler(that)
            ]);

                that.clipboard = new editorNS.Clipboard(this);

                that.undoRedoStack = new editorNS.UndoRedoStack();

                if (options && options.value) {
                    value = options.value;
                } else if (that.textarea) {
                    // indented HTML introduces problematic ranges in IE
                    value = element.val().replace(/[\r\n\v\f\t ]+/ig, " ");
                } else {
                    value = element[0].innerHTML;
                }

                that.value(value);

                $(document)
                .on("mousedown", proxy(that._endTyping, that))
                .on("mouseup", proxy(that._mouseup, that));

                kendo.notify(that);
            },

            _endTyping: function () {
                var keyboard = this.keyboard;

                try {
                    if (keyboard.isTypingInProgress()) {
                        keyboard.endTyping(true);

                        this.saveSelection();
                    }
                } catch (e) { }
            },

            _selectionChange: function () {
                if (!browser.msie) {
                    kendo.ui.editor.Dom.ensureTrailingBreaks(this.body);
                }

                this._selectionStarted = false;
                this.saveSelection();
                this.trigger("select", {});
            },

            _wrapTextarea: function () {
                var that = this,
                textarea = that.element,
                w = textarea[0].style.width,
                h = textarea[0].style.height,
                template = EditorUtils.editorWrapperTemplate,
                editorWrap = $(template).insertBefore(textarea).width(w).height(h),
                editArea = editorWrap.find(".k-editable-area");

                textarea.attr("autocomplete", "off")
                .appendTo(editArea).addClass("k-content k-raw-content").css("display", "none");

                that.textarea = textarea;
                that.wrapper = editorWrap;
            },

            _createContentElement: function (stylesheets) {
                var editor = this;
                var iframe, wnd, doc;
                var textarea = editor.textarea;
                var specifiedDomain = editor.options.domain;
                var domain = specifiedDomain || document.domain;
                var domainScript = "";
                var src = 'javascript:""';

                // automatically relax same-origin policy if document.domain != location.hostname,
                // or forcefully relax if options.domain is specified (for document.domain = document.domain scenario)
                if (specifiedDomain || domain != location.hostname) {
                    // relax same-origin policy
                    domainScript = "<script>document.domain=\"" + domain + "\"</script>";
                    src = "javascript:document.write('" + domainScript + "')";
                }

                textarea.hide();

                iframe = $("<iframe />", { frameBorder: "0" })[0];

                $(iframe)
                .css("display", "")
                .addClass("k-content")
                .insertBefore(textarea);


                iframe.src = src;

                wnd = iframe.contentWindow || iframe;
                doc = wnd.document || iframe.contentDocument;

                $(iframe).one("load", function () {
                    editor.toolbar.decorateFrom(doc.body);
                });

                doc.open();
                doc.write(
                "<!DOCTYPE html><html><head>" +
                "<meta charset='utf-8' />" +
                "<style>" +
                    "html,body{padding:0;margin:0;height:100%;min-height:100%;}" +
                    "body{font-size:12px;font-family:Verdana,Geneva,sans-serif;padding-top:1px;margin-top:-1px;" +
                    "word-wrap: break-word;-webkit-nbsp-mode: space;-webkit-line-break: after-white-space;" +
                    (kendo.support.isRtl(textarea) ? "direction:rtl;" : "") +
                    "}" +
                    "h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em}h3{font-size:1.16em}h4{font-size:1em}h5{font-size:.83em}h6{font-size:.7em}" +
                    "p{margin:0 0 1em;padding:0 .2em}.k-marker{display:none;}.k-paste-container,.Apple-style-span{position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden}" +
                    "ul,ol{padding-left:2.5em}" +
                    "span{-ms-high-contrast-adjust:none;}" +
                    "a{color:#00a}" +
                    "code{font-size:1.23em}" +
                    "telerik\\3Ascript{display: none;}" +
                    ".k-table{table-layout:fixed;width:100%;border-spacing:0;margin: 0 0 1em;}" +
                    ".k-table td{min-width:1px;padding:.2em .3em;}" +
                    ".k-table,.k-table td{outline:0;border: 1px dotted #ccc;}" +
                    ".k-table p{margin:0;padding:0;}" +
                "</style>" +
                domainScript +
                "<script>(function(d,c){d[c]('header'),d[c]('article'),d[c]('nav'),d[c]('section'),d[c]('footer');})(document, 'createElement');</script>" +
                $.map(stylesheets, function (href) {
                    return "<link rel='stylesheet' href='" + href + "'>";
                }).join("") +
                "</head><body autocorrect='off' contenteditable='true'></body></html>"
            );

                doc.close();

                return wnd;
            },

            _blur: function () {
                var textarea = this.textarea;
                var old = textarea ? textarea.val() : this._oldValue;
                var value = this.options.encoded ? this.encodedValue() : this.value();

                this.update();

                if (textarea) {
                    textarea.trigger("blur");
                }

                if (value != old) {
                    this.trigger("change");
                }
            },

            _initializeContentElement: function () {
                var editor = this;
                var doc;
                var blurTrigger;

                if (editor.textarea) {
                    editor.window = editor._createContentElement(editor.options.stylesheets);
                    doc = editor.document = editor.window.contentDocument || editor.window.document;
                    editor.body = doc.body;

                    blurTrigger = editor.window;

                    $(doc).on("mouseup" + NS, proxy(editor._mouseup, editor));
                } else {
                    editor.window = window;
                    doc = editor.document = document;
                    editor.body = editor.element[0];

                    blurTrigger = editor.body;

                    editor.toolbar.decorateFrom(editor.body);
                }

                $(blurTrigger).on("blur" + NS, proxy(this._blur, this));

                try {
                    doc.execCommand("enableInlineTableEditing", null, false);
                } catch (e) { }

                if (kendo.support.touch) {
                    $(doc).on("selectionchange" + NS, proxy(this._selectionChange, this))
                      .on("keydown" + NS, function () {
                          // necessary in iOS when touch events are bound to the page
                          if (kendo._activeElement() != doc.body) {
                              editor.window.focus();
                          }
                      });
                }

                $(editor.body)
                .on("keydown" + NS, function (e) {
                    var range;

                    if (e.keyCode === keys.F10) {
                        // Handling with timeout to avoid the default IE menu
                        setTimeout(proxy(editor.toolbar.focus, editor.toolbar), 100);

                        e.preventDefault();
                        return;
                    } else if (e.keyCode == keys.LEFT || e.keyCode == keys.RIGHT) {
                        // skip bom nodes when navigating with arrows
                        range = editor.getRange();
                        var left = e.keyCode == keys.LEFT;
                        var container = range[left ? "startContainer" : "endContainer"];
                        var offset = range[left ? "startOffset" : "endOffset"];
                        var direction = left ? -1 : 1;

                        if (left) {
                            offset -= 1;
                        }

                        if (offset + direction > 0 && container.nodeType == 3 && container.nodeValue[offset] == "\ufeff") {
                            range.setStart(container, offset + direction);
                            range.collapse(true);
                            editor.selectRange(range);
                        }
                    }

                    var toolName = editor.keyboard.toolFromShortcut(editor.toolbar.tools, e);

                    if (toolName) {
                        e.preventDefault();
                        if (!/^(undo|redo)$/.test(toolName)) {
                            editor.keyboard.endTyping(true);
                        }
                        editor.trigger("keydown", e);
                        editor.exec(toolName);
                        return false;
                    }

                    editor.keyboard.clearTimeout();

                    editor.keyboard.keydown(e);
                })
                .on("keyup" + NS, function (e) {
                    var selectionCodes = [8, 9, 33, 34, 35, 36, 37, 38, 39, 40, 40, 45, 46];

                    if ($.inArray(e.keyCode, selectionCodes) > -1 || (e.keyCode == 65 && e.ctrlKey && !e.altKey && !e.shiftKey)) {
                        editor._selectionChange();
                    }

                    editor.keyboard.keyup(e);
                })
                .on("mousedown" + NS, function (e) {
                    editor._selectionStarted = true;

                    // handle middle-click and ctrl-click on links
                    if (browser.gecko) {
                        return;
                    }

                    var target = $(e.target);

                    if ((e.which == 2 || (e.which == 1 && e.ctrlKey)) &&
                        target.is("a[href]")) {
                        window.open(target.attr("href"), "_new");
                    }
                })
                .on("click" + NS, function (e) {
                    var dom = kendo.ui.editor.Dom, range;

                    if (dom.name(e.target) === "img") {
                        range = editor.createRange();
                        range.selectNode(e.target);
                        editor.selectRange(range);
                    }
                })
                .on("cut" + NS + " paste" + NS, function (e) {
                    editor.clipboard["on" + e.type](e);
                })
                .on("focusin" + NS, function () {
                    $(this).addClass("k-state-active");
                    editor.toolbar.show();

                    if (editor.textarea) {
                        // see https://github.com/telerik/kendo/issues/3509
                        editor.selectRange(editor.getRange());
                    }
                })
                .on("focusout" + NS, function () {
                    setTimeout(function () {
                        var active = kendo._activeElement();
                        var body = editor.body;
                        var toolbar = editor.toolbar;

                        if (active != body && !$.contains(body, active) && !$(active).is(".k-editortoolbar-dragHandle") && !toolbar.focused()) {
                            $(body).removeClass("k-state-active");
                            toolbar.hide();
                        }
                    }, 10);
                });
            },

            _mouseup: function () {
                var that = this;

                if (that._selectionStarted) {
                    setTimeout(function () {
                        that._selectionChange();
                    }, 1);
                }
            },


            refresh: function () {
                var that = this;

                if (that.textarea) {
                    // preserve updated value before re-initializing
                    // don't use update() to prevent the editor from encoding the content too early
                    that.textarea.val(that.value());
                    that.wrapper.find("iframe").remove();
                    that._initializeContentElement(that);
                    that.value(that.textarea.val());
                }
            },

            events: [
            "select",
            "change",
            "execute",
            "error",
            "paste",
            "keydown",
            "keyup"
        ],

            options: {
                name: "Editor",
                messages: messages,
                formats: {},
                encoded: true,
                domain: null,
                serialization: {
                    entities: true,
                    scripts: false
                },
                stylesheets: [],
                dialogOptions: {
                    modal: true, resizable: false, draggable: true,
                    animation: false
                },
                fontName: [
                { text: "Arial", value: "Arial,Helvetica,sans-serif" },
                { text: "Courier New", value: "'Courier New',Courier,monospace" },
                { text: "Georgia", value: "Georgia,serif" },
                { text: "Impact", value: "Impact,Charcoal,sans-serif" },
                { text: "Lucida Console", value: "'Lucida Console',Monaco,monospace" },
                { text: "Tahoma", value: "Tahoma,Geneva,sans-serif" },
                { text: "Times New Roman", value: "'Times New Roman',Times,serif" },
                { text: "Trebuchet MS", value: "'Trebuchet MS',Helvetica,sans-serif" },
                { text: "Verdana", value: "Verdana,Geneva,sans-serif" }
            ],
                fontSize: [
                { text: "1 (8pt)", value: "xx-small" },
                { text: "2 (10pt)", value: "x-small" },
                { text: "3 (12pt)", value: "small" },
                { text: "4 (14pt)", value: "medium" },
                { text: "5 (18pt)", value: "large" },
                { text: "6 (24pt)", value: "x-large" },
                { text: "7 (36pt)", value: "xx-large" }
            ],
                formatBlock: [
                { text: "Paragraph", value: "p" },
                { text: "Quotation", value: "blockquote" },
                { text: "Heading 1", value: "h1" },
                { text: "Heading 2", value: "h2" },
                { text: "Heading 3", value: "h3" },
                { text: "Heading 4", value: "h4" },
                { text: "Heading 5", value: "h5" },
                { text: "Heading 6", value: "h6" }
            ],
                tools: [].concat.call(
                ["formatting"],
                toolGroups.basic,
                toolGroups.alignment,
                toolGroups.lists,
                toolGroups.indenting,
                toolGroups.links,
                ["insertImage"],
                toolGroups.tables
            )
            },

            destroy: function () {
                var that = this;
                Widget.fn.destroy.call(that);

                $(that.window)
                .add(that.document)
                .add(that.body)
                .add(that.wrapper)
                .add(that.element.closest("form"))
                .off(NS);

                $(document).off("mousedown", proxy(that._endTyping, that))
                       .off("mouseup", proxy(that._mouseup, that));

                that._focusOutside();

                that.toolbar.destroy();

                kendo.destroy(that.wrapper);
            },

            _focusOutside: function () {
                // move focus outside the Editor, see https://github.com/telerik/kendo/issues/3673
                if (kendo.support.browser.msie && this.textarea) {
                    var tempInput = $("<input style='position:absolute;left:-10px;top:-10px;width:1px;height:1px;font-size:0;border:0;' />").appendTo(document.body).focus();
                    tempInput.blur().remove();
                }
            },

            state: function (toolName) {
                var tool = Editor.defaultTools[toolName];
                var finder = tool && (tool.options.finder || tool.finder);
                var RangeUtils = kendo.ui.editor.RangeUtils;
                var range, textNodes;

                if (finder) {
                    range = this.getRange();

                    textNodes = RangeUtils.textNodes(range);

                    if (!textNodes.length && range.collapsed) {
                        textNodes = [range.startContainer];
                    }

                    return finder.getFormat ? finder.getFormat(textNodes) : finder.isFormatted(textNodes);
                }

                return false;
            },

            value: function (html) {
                var body = this.body,
                editorNS = kendo.ui.editor,
                dom = editorNS.Dom,
                currentHtml = editorNS.Serializer.domToXhtml(body, this.options.serialization);

                if (html === undefined) {
                    return currentHtml;
                }

                if (html == currentHtml) {
                    return;
                }

                // BEGIN EDIT: (3GiG: 2015-02-13)
                // Added this.options.serialization as third parameter.
                editorNS.Serializer.htmlToDom(html, body, this.options.serialization);

                if (!browser.msie) {
                    kendo.ui.editor.Dom.ensureTrailingBreaks(this.body);
                }

                this.selectionRestorePoint = null;
                this.update();

                this.toolbar.refreshTools();
            },

            saveSelection: function (range) {
                range = range || this.getRange();
                var container = range.commonAncestorContainer,
                body = this.body;

                if (container == body || $.contains(body, container)) {
                    this.selectionRestorePoint = new kendo.ui.editor.RestorePoint(range);
                }
            },

            _focusBody: function () {
                var body = this.body;
                var iframe = this.wrapper && this.wrapper.find("iframe")[0];
                var documentElement = this.document.documentElement;
                var activeElement = kendo._activeElement();

                if (activeElement != body && activeElement != iframe) {
                    var scrollTop = documentElement.scrollTop;
                    body.focus();
                    documentElement.scrollTop = scrollTop;
                }
            },

            restoreSelection: function () {
                this._focusBody();

                if (this.selectionRestorePoint) {
                    this.selectRange(this.selectionRestorePoint.toRange());
                }
            },

            focus: function () {
                this.restoreSelection();
            },

            update: function (value) {
                value = value || this.options.encoded ? this.encodedValue() : this.value();

                if (this.textarea) {
                    this.textarea.val(value);
                } else {
                    this._oldValue = value;
                }
            },

            encodedValue: function () {
                return kendo.ui.editor.Dom.encode(this.value());
            },

            createRange: function (document) {
                return kendo.ui.editor.RangeUtils.createRange(document || this.document);
            },

            getSelection: function () {
                return kendo.ui.editor.SelectionUtils.selectionFromDocument(this.document);
            },

            selectRange: function (range) {
                this._focusBody();
                var selection = this.getSelection();
                selection.removeAllRanges();
                selection.addRange(range);
                this.saveSelection(range);
            },

            getRange: function () {
                var selection = this.getSelection(),
                range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : this.createRange(),
                doc = this.document;

                if (range.startContainer == doc && range.endContainer == doc && !range.startOffset && !range.endOffset) {
                    range.setStart(this.body, 0);
                    range.collapse(true);
                }

                return range;
            },

            selectedHtml: function () {
                return kendo.ui.editor.Serializer.domToXhtml(this.getRange().cloneContents());
            },

            paste: function (html, options) {
                this.clipboard.paste(html, options);
            },

            exec: function (name, params) {
                var that = this,
                range,
                tool, command = null;

                if (!name) {
                    throw new Error("kendoEditor.exec(): `name` parameter cannot be empty");
                }

                name = name.toLowerCase();

                // restore selection
                if (!that.keyboard.isTypingInProgress()) {
                    that.restoreSelection();
                }

                tool = that.toolbar.toolById(name);

                if (!tool) {
                    // execute non-toolbar tool
                    for (var id in Editor.defaultTools) {
                        if (id.toLowerCase() == name) {
                            tool = Editor.defaultTools[id];
                            break;
                        }
                    }
                }

                if (tool) {
                    range = that.getRange();

                    if (tool.command) {
                        command = tool.command(extend({ range: range }, params));
                    }

                    that.trigger("execute", { name: name, command: command });

                    if (/^(undo|redo)$/i.test(name)) {
                        that.undoRedoStack[name]();
                    } else if (command) {
                        if (!command.managesUndoRedo) {
                            that.undoRedoStack.push(command);
                        }

                        command.editor = that;
                        command.exec();

                        if (command.async) {
                            command.change = proxy(that._selectionChange, that);
                            return;
                        }
                    }

                    that._selectionChange();
                }
            }
        });

        Editor.defaultTools = {
            undo: { options: { key: "Z", ctrl: true} },
            redo: { options: { key: "Y", ctrl: true} }
        };

        kendo.ui.plugin(Editor);

        var Tool = Class.extend({
            init: function (options) {
                this.options = options;
            },

            initialize: function (ui, options) {
                ui.attr({ unselectable: "on", title: options.title });
            },

            command: function (commandArguments) {
                return new this.options.command(commandArguments);
            },

            update: $.noop
        });

        Tool.exec = function (editor, name, value) {
            editor.exec(name, { value: value });
        };

        var FormatTool = Tool.extend({
            init: function (options) {
                Tool.fn.init.call(this, options);
            },

            command: function (commandArguments) {
                var that = this;
                return new kendo.ui.editor.FormatCommand(extend(commandArguments, {
                    formatter: that.options.formatter
                }));
            },

            update: function (ui, nodes) {
                var isFormatted = this.options.finder.isFormatted(nodes);

                ui.toggleClass("k-state-selected", isFormatted);
                ui.attr("aria-pressed", isFormatted);
            }
        });

        EditorUtils.registerTool("separator", new Tool({ template: new ToolTemplate({ template: EditorUtils.separatorTemplate }) }));

        // Exports ================================================================

        var bomFill = browser.msie && browser.version < 9 ? '\ufeff' : '';
        var emptyElementContent = '<br class="k-br" />';

        if (browser.msie) {
            if (browser.version < 10) {
                emptyElementContent = '\ufeff';
            } else if (browser.version < 11) {
                emptyElementContent = ' '; // allow up/down arrows to focus empty rows
            }
        }

        extend(kendo.ui, {
            editor: {
                ToolTemplate: ToolTemplate,
                EditorUtils: EditorUtils,
                Tool: Tool,
                FormatTool: FormatTool,
                _bomFill: bomFill,
                emptyElementContent: emptyElementContent
            }
        });

    })(window.jQuery);

    (function ($) {

        var kendo = window.kendo,
    map = $.map,
    extend = $.extend,
    browser = kendo.support.browser,
    STYLE = "style",
    FLOAT = "float",
    CSSFLOAT = "cssFloat",
    STYLEFLOAT = "styleFloat",
    CLASS = "class",
    KMARKER = "k-marker";

        function makeMap(items) {
            var obj = {},
        i, len;

            for (i = 0, len = items.length; i < len; i++) {
                obj[items[i]] = true;
            }
            return obj;
        }

        var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed".split(",")),
    nonListBlockElements = "div,p,h1,h2,h3,h4,h5,h6,address,applet,blockquote,button,center,dd,dir,dl,dt,fieldset,form,frameset,hr,iframe,isindex,map,menu,noframes,noscript,object,pre,script,table,tbody,td,tfoot,th,thead,tr,header,article,nav,footer,section,aside,main,figure,figcaption".split(","),
    blockElements = nonListBlockElements.concat(["ul", "ol", "li"]),
    block = makeMap(blockElements),
    inlineElements = "span,em,a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,strike,strong,sub,sup,textarea,tt,u,var,data,time,mark,ruby".split(","),
    inline = makeMap(inlineElements),
    fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected".split(","));

        var normalize = function (node) {
            if (node.nodeType == 1) {
                node.normalize();
            }
        };

        if (browser.msie && browser.version >= 8) {
            normalize = function (parent) {
                if (parent.nodeType == 1 && parent.firstChild) {
                    var prev = parent.firstChild,
                node = prev;

                    while (true) {
                        node = node.nextSibling;

                        if (!node) {
                            break;
                        }

                        if (node.nodeType == 3 && prev.nodeType == 3) {
                            node.nodeValue = prev.nodeValue + node.nodeValue;
                            Dom.remove(prev);
                        }

                        prev = node;
                    }
                }
            };
        }

        var whitespace = /^\s+$/,
    rgb = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i,
    bom = /\ufeff/g,
    whitespaceOrBom = /^(\s+|\ufeff)$/,
    persistedScrollTop,
    cssAttributes =
           ("color,padding-left,padding-right,padding-top,padding-bottom," +
            "background-color,background-attachment,background-image,background-position,background-repeat," +
            "border-top-style,border-top-width,border-top-color," +
            "border-bottom-style,border-bottom-width,border-bottom-color," +
            "border-left-style,border-left-width,border-left-color," +
            "border-right-style,border-right-width,border-right-color," +
            "font-family,font-size,font-style,font-variant,font-weight,line-height"
           ).split(","),
    htmlRe = /[<>\&]/g,
    entityRe = /[\u00A0-\u2666<>\&]/g,
    entityTable = {
        34: 'quot', 38: 'amp', 39: 'apos', 60: 'lt', 62: 'gt',
        160: 'nbsp', 161: 'iexcl', 162: 'cent', 163: 'pound', 164: 'curren',
        165: 'yen', 166: 'brvbar', 167: 'sect', 168: 'uml', 169: 'copy',
        170: 'ordf', 171: 'laquo', 172: 'not', 173: 'shy', 174: 'reg',
        175: 'macr', 176: 'deg', 177: 'plusmn', 178: 'sup2', 179: 'sup3',
        180: 'acute', 181: 'micro', 182: 'para', 183: 'middot', 184: 'cedil',
        185: 'sup1', 186: 'ordm', 187: 'raquo', 188: 'frac14', 189: 'frac12',
        190: 'frac34', 191: 'iquest', 192: 'Agrave', 193: 'Aacute', 194: 'Acirc',
        195: 'Atilde', 196: 'Auml', 197: 'Aring', 198: 'AElig', 199: 'Ccedil',
        200: 'Egrave', 201: 'Eacute', 202: 'Ecirc', 203: 'Euml', 204: 'Igrave',
        205: 'Iacute', 206: 'Icirc', 207: 'Iuml', 208: 'ETH', 209: 'Ntilde',
        210: 'Ograve', 211: 'Oacute', 212: 'Ocirc', 213: 'Otilde', 214: 'Ouml',
        215: 'times', 216: 'Oslash', 217: 'Ugrave', 218: 'Uacute', 219: 'Ucirc',
        220: 'Uuml', 221: 'Yacute', 222: 'THORN', 223: 'szlig', 224: 'agrave',
        225: 'aacute', 226: 'acirc', 227: 'atilde', 228: 'auml', 229: 'aring',
        230: 'aelig', 231: 'ccedil', 232: 'egrave', 233: 'eacute', 234: 'ecirc',
        235: 'euml', 236: 'igrave', 237: 'iacute', 238: 'icirc', 239: 'iuml',
        240: 'eth', 241: 'ntilde', 242: 'ograve', 243: 'oacute', 244: 'ocirc',
        245: 'otilde', 246: 'ouml', 247: 'divide', 248: 'oslash', 249: 'ugrave',
        250: 'uacute', 251: 'ucirc', 252: 'uuml', 253: 'yacute', 254: 'thorn',
        255: 'yuml', 402: 'fnof', 913: 'Alpha', 914: 'Beta', 915: 'Gamma',
        916: 'Delta', 917: 'Epsilon', 918: 'Zeta', 919: 'Eta', 920: 'Theta',
        921: 'Iota', 922: 'Kappa', 923: 'Lambda', 924: 'Mu', 925: 'Nu',
        926: 'Xi', 927: 'Omicron', 928: 'Pi', 929: 'Rho', 931: 'Sigma',
        932: 'Tau', 933: 'Upsilon', 934: 'Phi', 935: 'Chi', 936: 'Psi',
        937: 'Omega', 945: 'alpha', 946: 'beta', 947: 'gamma', 948: 'delta',
        949: 'epsilon', 950: 'zeta', 951: 'eta', 952: 'theta', 953: 'iota',
        954: 'kappa', 955: 'lambda', 956: 'mu', 957: 'nu', 958: 'xi',
        959: 'omicron', 960: 'pi', 961: 'rho', 962: 'sigmaf', 963: 'sigma',
        964: 'tau', 965: 'upsilon', 966: 'phi', 967: 'chi', 968: 'psi',
        969: 'omega', 977: 'thetasym', 978: 'upsih', 982: 'piv', 8226: 'bull',
        8230: 'hellip', 8242: 'prime', 8243: 'Prime', 8254: 'oline', 8260: 'frasl',
        8472: 'weierp', 8465: 'image', 8476: 'real', 8482: 'trade', 8501: 'alefsym',
        8592: 'larr', 8593: 'uarr', 8594: 'rarr', 8595: 'darr', 8596: 'harr',
        8629: 'crarr', 8656: 'lArr', 8657: 'uArr', 8658: 'rArr', 8659: 'dArr',
        8660: 'hArr', 8704: 'forall', 8706: 'part', 8707: 'exist', 8709: 'empty',
        8711: 'nabla', 8712: 'isin', 8713: 'notin', 8715: 'ni', 8719: 'prod',
        8721: 'sum', 8722: 'minus', 8727: 'lowast', 8730: 'radic', 8733: 'prop',
        8734: 'infin', 8736: 'ang', 8743: 'and', 8744: 'or', 8745: 'cap',
        8746: 'cup', 8747: 'int', 8756: 'there4', 8764: 'sim', 8773: 'cong',
        8776: 'asymp', 8800: 'ne', 8801: 'equiv', 8804: 'le', 8805: 'ge',
        8834: 'sub', 8835: 'sup', 8836: 'nsub', 8838: 'sube', 8839: 'supe',
        8853: 'oplus', 8855: 'otimes', 8869: 'perp', 8901: 'sdot', 8968: 'lceil',
        8969: 'rceil', 8970: 'lfloor', 8971: 'rfloor', 9001: 'lang', 9002: 'rang',
        9674: 'loz', 9824: 'spades', 9827: 'clubs', 9829: 'hearts', 9830: 'diams',
        338: 'OElig', 339: 'oelig', 352: 'Scaron', 353: 'scaron', 376: 'Yuml',
        710: 'circ', 732: 'tilde', 8194: 'ensp', 8195: 'emsp', 8201: 'thinsp',
        8204: 'zwnj', 8205: 'zwj', 8206: 'lrm', 8207: 'rlm', 8211: 'ndash',
        8212: 'mdash', 8216: 'lsquo', 8217: 'rsquo', 8218: 'sbquo', 8220: 'ldquo',
        8221: 'rdquo', 8222: 'bdquo', 8224: 'dagger', 8225: 'Dagger', 8240: 'permil',
        8249: 'lsaquo', 8250: 'rsaquo', 8364: 'euro'
    };

        var Dom = {
            block: block,
            inline: inline,

            findNodeIndex: function (node, skipText) {
                var i = 0;

                if (!node) {
                    return -1;
                }

                while (true) {
                    node = node.previousSibling;

                    if (!node) {
                        break;
                    }

                    if (!(skipText && node.nodeType == 3)) {
                        i++;
                    }
                }

                return i;
            },

            isDataNode: function (node) {
                return node && node.nodeValue !== null && node.data !== null;
            },

            isAncestorOf: function (parent, node) {
                try {
                    return !Dom.isDataNode(parent) && ($.contains(parent, Dom.isDataNode(node) ? node.parentNode : node) || node.parentNode == parent);
                } catch (e) {
                    return false;
                }
            },

            isAncestorOrSelf: function (root, node) {
                return Dom.isAncestorOf(root, node) || root == node;
            },

            findClosestAncestor: function (root, node) {
                if (Dom.isAncestorOf(root, node)) {
                    while (node && node.parentNode != root) {
                        node = node.parentNode;
                    }
                }

                return node;
            },

            getNodeLength: function (node) {
                return Dom.isDataNode(node) ? node.length : node.childNodes.length;
            },

            splitDataNode: function (node, offset) {
                var newNode = node.cloneNode(false),
            denormalizedText = "",
            iterator = node;

                while (iterator.nextSibling && iterator.nextSibling.nodeType == 3 && iterator.nextSibling.nodeValue) {
                    denormalizedText += iterator.nextSibling.nodeValue;
                    iterator = iterator.nextSibling;
                }

                node.deleteData(offset, node.length);
                newNode.deleteData(0, offset);
                newNode.nodeValue += denormalizedText;
                Dom.insertAfter(newNode, node);
            },

            attrEquals: function (node, attributes) {
                for (var key in attributes) {
                    var value = node[key];

                    if (key == FLOAT) {
                        value = node[kendo.support.cssFloat ? CSSFLOAT : STYLEFLOAT];
                    }

                    if (typeof value == "object") {
                        if (!Dom.attrEquals(value, attributes[key])) {
                            return false;
                        }
                    } else if (value != attributes[key]) {
                        return false;
                    }
                }

                return true;
            },

            blockParentOrBody: function (node) {
                return Dom.parentOfType(node, blockElements) || node.ownerDocument.body;
            },

            blockParents: function (nodes) {
                var blocks = [],
            i, len;

                for (i = 0, len = nodes.length; i < len; i++) {
                    var block = Dom.parentOfType(nodes[i], Dom.blockElements);
                    if (block && $.inArray(block, blocks) < 0) {
                        blocks.push(block);
                    }
                }

                return blocks;
            },

            windowFromDocument: function (document) {
                return document.defaultView || document.parentWindow;
            },

            normalize: normalize,
            blockElements: blockElements,
            nonListBlockElements: nonListBlockElements,
            inlineElements: inlineElements,
            empty: empty,
            fillAttrs: fillAttrs,

            toHex: function (color) {
                var matches = rgb.exec(color);

                if (!matches) {
                    return color;
                }

                return "#" + map(matches.slice(1), function (x) {
                    x = parseInt(x, 10).toString(16);
                    return x.length > 1 ? x : "0" + x;
                }).join("");
            },

            encode: function (value, options) {
                var encodableChars = (!options || options.entities) ? entityRe : htmlRe;
                return value.replace(encodableChars, function (c) {
                    var charCode = c.charCodeAt(0);
                    var entity = entityTable[charCode];
                    return entity ? '&' + entity + ';' : c;
                });
            },

            stripBom: function (text) {
                return (text || "").replace(bom, "");
            },

            insignificant: function (node) {
                var attr = node.attributes;

                return node.className == "k-marker" || (Dom.is(node, 'br') && (node.className == "k-br" || attr._moz_dirty || attr._moz_editor_bogus_node));
            },

            emptyNode: function (node) {
                var significantNodes = $.grep(node.childNodes, function (child) {
                    if (Dom.is(child, 'br')) {
                        return false;
                    } else if (Dom.insignificant(child)) {
                        return false;
                    } else if (child.nodeType == 3 && whitespaceOrBom.test(child.nodeValue)) {
                        return false;
                    } else if (Dom.is(child, 'p') && Dom.emptyNode(child)) {
                        return false;
                    }

                    return true;
                });

                return !significantNodes.length;
            },

            name: function (node) {
                return node.nodeName.toLowerCase();
            },

            significantChildNodes: function (node) {
                return $.grep(node.childNodes, function (child) {
                    return child.nodeType != 3 || !Dom.isWhitespace(child);
                });
            },

            lastTextNode: function (node) {
                var result = null;

                if (node.nodeType == 3) {
                    return node;
                }

                for (var child = node.lastChild; child; child = child.previousSibling) {
                    result = Dom.lastTextNode(child);

                    if (result) {
                        return result;
                    }
                }

                return result;
            },

            is: function (node, nodeName) {
                return Dom.name(node) == nodeName;
            },

            isMarker: function (node) {
                return node.className == KMARKER;
            },

            isWhitespace: function (node) {
                return whitespace.test(node.nodeValue);
            },

            isBlock: function (node) {
                return block[Dom.name(node)];
            },

            isEmpty: function (node) {
                return empty[Dom.name(node)];
            },

            isInline: function (node) {
                return inline[Dom.name(node)];
            },

            scrollContainer: function (doc) {
                var wnd = Dom.windowFromDocument(doc),
            scrollContainer = (wnd.contentWindow || wnd).document || wnd.ownerDocument || wnd;

                if (kendo.support.browser.webkit || scrollContainer.compatMode == 'BackCompat') {
                    scrollContainer = scrollContainer.body;
                } else {
                    scrollContainer = scrollContainer.documentElement;
                }

                return scrollContainer;
            },

            scrollTo: function (node) {
                var element = $(Dom.isDataNode(node) ? node.parentNode : node),
            wnd = Dom.windowFromDocument(node.ownerDocument),
            windowHeight = wnd.innerHeight,
            elementTop, elementHeight,
            scrollContainer = Dom.scrollContainer(node.ownerDocument);

                if (Dom.name(element[0]) == "br") {
                    element = element.parent();
                }

                elementTop = element.offset().top;
                elementHeight = element[0].offsetHeight;

                if (elementHeight + elementTop > scrollContainer.scrollTop + windowHeight) {
                    scrollContainer.scrollTop = elementHeight + elementTop - windowHeight;
                }
            },

            persistScrollTop: function (doc) {
                persistedScrollTop = Dom.scrollContainer(doc).scrollTop;
            },

            restoreScrollTop: function (doc) {
                Dom.scrollContainer(doc).scrollTop = persistedScrollTop;
            },

            insertAt: function (parent, newElement, position) {
                parent.insertBefore(newElement, parent.childNodes[position] || null);
            },

            insertBefore: function (newElement, referenceElement) {
                if (referenceElement.parentNode) {
                    return referenceElement.parentNode.insertBefore(newElement, referenceElement);
                } else {
                    return referenceElement;
                }
            },

            insertAfter: function (newElement, referenceElement) {
                return referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);
            },

            remove: function (node) {
                node.parentNode.removeChild(node);
            },

            removeTextSiblings: function (node) {
                var parentNode = node.parentNode;

                while (node.nextSibling && node.nextSibling.nodeType == 3) {
                    parentNode.removeChild(node.nextSibling);
                }

                while (node.previousSibling && node.previousSibling.nodeType == 3) {
                    parentNode.removeChild(node.previousSibling);
                }
            },

            trim: function (parent) {
                for (var i = parent.childNodes.length - 1; i >= 0; i--) {
                    var node = parent.childNodes[i];
                    if (Dom.isDataNode(node)) {
                        if (!Dom.stripBom(node.nodeValue).length) {
                            Dom.remove(node);
                        }

                        if (Dom.isWhitespace(node)) {
                            Dom.insertBefore(node, parent);
                        }
                    } else if (node.className != KMARKER) {
                        Dom.trim(node);
                        if (!node.childNodes.length && !Dom.isEmpty(node)) {
                            Dom.remove(node);
                        }
                    }
                }

                return parent;
            },

            closest: function (node, tag) {
                while (node && Dom.name(node) != tag) {
                    node = node.parentNode;
                }

                return node;
            },

            sibling: function (node, direction) {
                do {
                    node = node[direction];
                } while (node && node.nodeType != 1);

                return node;
            },

            next: function (node) {
                return Dom.sibling(node, "nextSibling");
            },

            prev: function (node) {
                return Dom.sibling(node, "previousSibling");
            },

            parentOfType: function (node, tags) {
                do {
                    node = node.parentNode;
                } while (node && !(Dom.ofType(node, tags)));

                return node;
            },

            ofType: function (node, tags) {
                return $.inArray(Dom.name(node), tags) >= 0;
            },

            changeTag: function (referenceElement, tagName, skipAttributes) {
                var newElement = Dom.create(referenceElement.ownerDocument, tagName),
            attributes = referenceElement.attributes,
            i, len, name, value, attribute;

                if (!skipAttributes) {
                    for (i = 0, len = attributes.length; i < len; i++) {
                        attribute = attributes[i];
                        if (attribute.specified) {
                            // IE < 8 cannot set class or style via setAttribute
                            name = attribute.nodeName;
                            value = attribute.nodeValue;
                            if (name == CLASS) {
                                newElement.className = value;
                            } else if (name == STYLE) {
                                newElement.style.cssText = referenceElement.style.cssText;
                            } else {
                                newElement.setAttribute(name, value);
                            }
                        }
                    }
                }

                while (referenceElement.firstChild) {
                    newElement.appendChild(referenceElement.firstChild);
                }

                Dom.insertBefore(newElement, referenceElement);
                Dom.remove(referenceElement);
                return newElement;
            },

            editableParent: function (node) {
                while (node && (node.nodeType == 3 || node.contentEditable !== 'true')) {
                    node = node.parentNode;
                }

                return node;
            },

            wrap: function (node, wrapper) {
                Dom.insertBefore(wrapper, node);
                wrapper.appendChild(node);
                return wrapper;
            },

            unwrap: function (node) {
                var parent = node.parentNode;
                while (node.firstChild) {
                    parent.insertBefore(node.firstChild, node);
                }

                parent.removeChild(node);
            },

            create: function (document, tagName, attributes) {
                return Dom.attr(document.createElement(tagName), attributes);
            },

            attr: function (element, attributes) {
                attributes = extend({}, attributes);

                if (attributes && STYLE in attributes) {
                    Dom.style(element, attributes.style);
                    delete attributes.style;
                }

                for (var attr in attributes) {
                    if (attributes[attr] === null) {
                        element.removeAttribute(attr);
                        delete attributes[attr];
                    } else if (attr == "className") {
                        element[attr] = attributes[attr];
                    }
                }

                return extend(element, attributes);
            },

            style: function (node, value) {
                $(node).css(value || {});
            },

            unstyle: function (node, value) {
                for (var key in value) {
                    if (key == FLOAT) {
                        key = kendo.support.cssFloat ? CSSFLOAT : STYLEFLOAT;
                    }

                    node.style[key] = "";
                }

                if (node.style.cssText === "") {
                    node.removeAttribute(STYLE);
                }
            },

            inlineStyle: function (body, name, attributes) {
                var span = $(Dom.create(body.ownerDocument, name, attributes)),
            style;

                body.appendChild(span[0]);

                style = map(cssAttributes, function (value) {
                    if (browser.msie && value == "line-height" && span.css(value) == "1px") {
                        return "line-height:1.5";
                    } else {
                        return value + ":" + span.css(value);
                    }
                }).join(";");

                span.remove();

                return style;
            },

            getEffectiveBackground: function (element) {
                var backgroundStyle = element.css("background-color");

                if (backgroundStyle.indexOf("rgba(0, 0, 0, 0") < 0 && backgroundStyle !== "transparent") {
                    return backgroundStyle;
                } else if (element[0].tagName.toLowerCase() === "html") {
                    return "Window";
                } else {
                    return Dom.getEffectiveBackground(element.parent());
                }
            },

            removeClass: function (node, classNames) {
                var className = " " + node.className + " ",
            classes = classNames.split(" "),
            i, len;

                for (i = 0, len = classes.length; i < len; i++) {
                    className = className.replace(" " + classes[i] + " ", " ");
                }

                className = $.trim(className);

                if (className.length) {
                    node.className = className;
                } else {
                    node.removeAttribute(CLASS);
                }
            },

            commonAncestor: function () {
                var count = arguments.length,
            paths = [],
            minPathLength = Infinity,
            output = null,
            i, ancestors, node, first, j;

                if (!count) {
                    return null;
                }

                if (count == 1) {
                    return arguments[0];
                }

                for (i = 0; i < count; i++) {
                    ancestors = [];
                    node = arguments[i];
                    while (node) {
                        ancestors.push(node);
                        node = node.parentNode;
                    }
                    paths.push(ancestors.reverse());
                    minPathLength = Math.min(minPathLength, ancestors.length);
                }

                if (count == 1) {
                    return paths[0][0];
                }

                for (i = 0; i < minPathLength; i++) {
                    first = paths[0][i];

                    for (j = 1; j < count; j++) {
                        if (first != paths[j][i]) {
                            return output;
                        }
                    }

                    output = first;
                }
                return output;
            },

            closestSplittableParent: function (nodes) {
                var result;

                if (nodes.length == 1) {
                    result = Dom.parentOfType(nodes[0], ["ul", "ol"]);
                } else {
                    result = Dom.commonAncestor.apply(null, nodes);
                }

                if (!result) {
                    result = Dom.parentOfType(nodes[0], ["p", "td"]) || nodes[0].ownerDocument.body;
                }

                if (Dom.isInline(result)) {
                    result = Dom.blockParentOrBody(result);
                }

                var editableParents = map(nodes, Dom.editableParent);
                var editableAncestor = Dom.commonAncestor(editableParents)[0];

                if ($.contains(result, editableAncestor)) {
                    result = editableAncestor;
                }

                return result;
            },

            closestEditable: function (node, types) {
                var closest = Dom.parentOfType(node, types);
                var editable = Dom.editableParent(node);

                if (closest && editable && $.contains(closest, editable)) {
                    closest = editable;
                } else if (!closest && editable) {
                    closest = editable;
                }

                return closest;
            },

            closestEditableOfType: function (node, types) {
                var editable = Dom.closestEditable(node, types);

                if (editable && Dom.ofType(editable, types)) {
                    return editable;
                }
            },

            filter: function (tagName, nodes, invert) {
                var i = 0;
                var len = nodes.length;
                var result = [];
                var name;

                for (; i < len; i++) {
                    name = Dom.name(nodes[i]);
                    if ((!invert && name == tagName) || (invert && name != tagName)) {
                        result.push(nodes[i]);
                    }
                }

                return result;
            },

            ensureTrailingBreaks: function (node) {
                var elements = $(node).find("p,td,th");
                var length = elements.length;
                var i = 0;

                if (length) {
                    for (; i < length; i++) {
                        Dom.ensureTrailingBreak(elements[i]);
                    }
                } else {
                    Dom.ensureTrailingBreak(node);
                }
            },

            ensureTrailingBreak: function (node) {
                var lastChild = node.lastChild;
                var name = lastChild && Dom.name(lastChild);
                var br;

                if (!name ||
            (name != "br" && name != "img") ||
            (name == "br" && lastChild.className != "k-br")) {
                    br = node.ownerDocument.createElement("br");
                    br.className = "k-br";
                    node.appendChild(br);
                }
            }
        };

        kendo.ui.editor.Dom = Dom;

    })(window.kendo.jQuery);

    (function ($, undefined) {

        // Imports ================================================================
        var kendo = window.kendo;
        var Editor = kendo.ui.editor;
        var dom = Editor.Dom;
        var extend = $.extend;

        var fontSizeMappings = 'xx-small,x-small,small,medium,large,x-large,xx-large'.split(',');
        var quoteRe = /"/g; //"
        var brRe = /<br[^>]*>/i;
        var pixelRe = /^\d+(\.\d*)?(px)?$/i;
        var emptyPRe = /<p><\/p>/i;
        var cssDeclaration = /([\w|\-]+)\s*:\s*([^;]+);?/i;
        var sizzleAttr = /^sizzle-\d+/i;
        var scriptAttr = /^k-script-/i;
        var onerrorRe = /\s*onerror\s*=\s*(?:'|")?([^'">\s]*)(?:'|")?/i;

        var div = document.createElement("div");
        div.innerHTML = " <hr>";
        var supportsLeadingWhitespace = div.firstChild.nodeType === 3;
        div = null;

        var Serializer = {
            toEditableHtml: function (html) {
                var br = '<br class="k-br">';

                html = html || "";

                return html
            .replace(/<!\[CDATA\[(.*)?\]\]>/g, "<!--[CDATA[$1]]-->")
            .replace(/<script([^>]*)>(.*)?<\/script>/ig, "<telerik:script$1>$2<\/telerik:script>")
            .replace(/<img([^>]*)>/ig, function (match) {
                return match.replace(onerrorRe, "");
            })
            .replace(/(<\/?img[^>]*>)[\r\n\v\f\t ]+/ig, "$1")
            .replace(/^<(table|blockquote)/i, br + '<$1')
            .replace(/<\/(table|blockquote)>$/i, '</$1>' + br);
            },

            _fillEmptyElements: function (body) {
                // fills empty elements to allow them to be focused
                $(body).find("p").each(function () {
                    var p = $(this);
                    if (/^\s*$/g.test(p.text()) && !p.find("img,input").length) {
                        var node = this;
                        while (node.firstChild && node.firstChild.nodeType != 3) {
                            node = node.firstChild;
                        }

                        if (node.nodeType == 1 && !dom.empty[dom.name(node)]) {
                            node.innerHTML = kendo.ui.editor.emptyElementContent;
                        }
                    }
                });
            },

            _removeSystemElements: function (body) {
                // removes persisted system elements
                $(".k-paste-container", body).remove();
            },

            _resetOrderedLists: function (root) {
                // fix for IE9 OL bug -- https://connect.microsoft.com/IE/feedback/details/657695/ordered-list-numbering-changes-from-correct-to-0-0
                var ols = root.getElementsByTagName("ol"), i, ol, originalStart;

                for (i = 0; i < ols.length; i++) {
                    ol = ols[i];
                    originalStart = ol.getAttribute("start");

                    ol.setAttribute("start", 1);

                    if (originalStart) {
                        ol.setAttribute("start", originalStart);
                    } else {
                        ol.removeAttribute(originalStart);
                    }
                }
            },

            _preventScriptExecution: function (root) {
                $(root).find("*").each(function () {
                    var attributes = this.attributes;
                    var attribute, i, l, name;

                    for (i = 0, l = attributes.length; i < l; i++) {
                        attribute = attributes[i];
                        name = attribute.nodeName;

                        if (attribute.specified && /^on/i.test(name)) {
                            this.removeAttribute(name);
                            this.setAttribute("k-script-" + name, attribute.nodeValue);
                        }
                    }
                });
            },


            // BEGIN EDIT: (3GiG: 2015-02-13)
            // Added serializationOptions parameter.
            htmlToDom: function (html, root, serializationOptions) {
                var browser = kendo.support.browser;
                var msie = browser.msie;
                var legacyIE = msie && browser.version < 9;

                html = Serializer.toEditableHtml(html);

                if (legacyIE) {
                    // Internet Explorer removes comments from the beginning of the html
                    html = "<br/>" + html;

                    var originalSrc = "originalsrc",
                originalHref = "originalhref";

                    // IE < 8 makes href and src attributes absolute
                    html = html.replace(/href\s*=\s*(?:'|")?([^'">\s]*)(?:'|")?/, originalHref + '="$1"');
                    html = html.replace(/src\s*=\s*(?:'|")?([^'">\s]*)(?:'|")?/, originalSrc + '="$1"');

                }

                root.innerHTML = html;

                if (legacyIE) {
                    dom.remove(root.firstChild);

                    $(root).find("telerik\\:script,script,link,img,a").each(function () {
                        var node = this;
                        if (node[originalHref]) {
                            node.setAttribute("href", node[originalHref]);
                            node.removeAttribute(originalHref);
                        }
                        if (node[originalSrc]) {
                            node.setAttribute("src", node[originalSrc]);
                            node.removeAttribute(originalSrc);
                        }
                    });
                } else if (msie) {
                    // having unicode characters creates denormalized DOM tree in IE9
                    dom.normalize(root);

                    Serializer._resetOrderedLists(root);
                }

                // BEGIN EDIT: (3GiG: 2015-02-13)
                // Added serializationOptions.scripts check.
                if (serializationOptions.scripts !== true) {
                    Serializer._preventScriptExecution(root);
                }

                Serializer._fillEmptyElements(root);

                Serializer._removeSystemElements(root);

                // add k-table class to all tables
                $("table", root).addClass("k-table");

                return root;
            },

            domToXhtml: function (root, options) {
                var result = [];
                var tagMap = {
                    'telerik:script': {
                        start: function (node) { result.push('<script'); attr(node); result.push('>'); },
                        end: function () { result.push('</script>'); },
                        skipEncoding: true
                    },
                    b: {
                        start: function () { result.push('<strong>'); },
                        end: function () { result.push('</strong>'); }
                    },
                    i: {
                        start: function () { result.push('<em>'); },
                        end: function () { result.push('</em>'); }
                    },
                    u: {
                        start: function () { result.push('<span style="text-decoration:underline;">'); },
                        end: function () { result.push('</span>'); }
                    },
                    iframe: {
                        start: function (node) { result.push('<iframe'); attr(node); result.push('>'); },
                        end: function () { result.push('</iframe>'); }
                    },
                    font: {
                        start: function (node) {
                            result.push('<span style="');

                            var color = node.getAttribute('color');
                            var size = fontSizeMappings[node.getAttribute('size')];
                            var face = node.getAttribute('face');

                            if (color) {
                                result.push('color:');
                                result.push(dom.toHex(color));
                                result.push(';');
                            }

                            if (face) {
                                result.push('font-face:');
                                result.push(face);
                                result.push(';');
                            }

                            if (size) {
                                result.push('font-size:');
                                result.push(size);
                                result.push(';');
                            }

                            result.push('">');
                        },
                        end: function () {
                            result.push('</span>');
                        }
                    }
                };
                options = options || {};

                function styleAttr(cssText) {
                    // In IE < 8 the style attribute does not return proper nodeValue
                    var trim = $.trim;
                    var css = trim(cssText).split(';');
                    var i, length = css.length;
                    var match;
                    var property, value;

                    for (i = 0, length = css.length; i < length; i++) {
                        if (!css[i].length) {
                            continue;
                        }

                        match = cssDeclaration.exec(css[i]);

                        // IE8 does not provide a value for 'inherit'
                        if (!match) {
                            continue;
                        }

                        property = trim(match[1].toLowerCase());
                        value = trim(match[2]);

                        if (property == "font-size-adjust" || property == "font-stretch") {
                            continue;
                        }

                        if (property.indexOf('color') >= 0) {
                            value = dom.toHex(value);
                        } else if (property.indexOf('font') >= 0) {
                            value = value.replace(quoteRe, "'");
                        } else if (/\burl\(/g.test(value)) {
                            value = value.replace(quoteRe, "");
                        }

                        result.push(property);
                        result.push(':');
                        result.push(value);
                        result.push(';');
                    }
                }

                function attr(node) {
                    var specifiedAttributes = [];
                    var attributes = node.attributes;
                    var attribute, i, l;
                    var name, value, specified;

                    if (dom.is(node, 'img')) {
                        var width = node.style.width,
                    height = node.style.height,
                    $node = $(node);

                        if (width && pixelRe.test(width)) {
                            $node.attr('width', parseInt(width, 10));
                            dom.unstyle(node, { width: undefined });
                        }

                        if (height && pixelRe.test(height)) {
                            $node.attr('height', parseInt(height, 10));
                            dom.unstyle(node, { height: undefined });
                        }
                    }

                    for (i = 0, l = attributes.length; i < l; i++) {
                        attribute = attributes[i];

                        name = attribute.nodeName;
                        value = attribute.nodeValue;
                        specified = attribute.specified;

                        // In IE < 8 the 'value' attribute is not returned as 'specified'. The same goes for type="text"
                        if (name == 'value' && 'value' in node && node.value) {
                            specified = true;
                        } else if (name == 'type' && value == 'text') {
                            specified = true;
                        } else if (name == "class" && !value) {
                            specified = false;
                        } else if (sizzleAttr.test(name)) {
                            specified = false;
                        } else if (name == 'complete') {
                            specified = false;
                        } else if (name == 'altHtml') {
                            specified = false;
                        } else if (name == 'start' && (dom.is(node, "ul") || dom.is(node, "ol"))) {
                            specified = false;
                        } else if (name.indexOf('_moz') >= 0) {
                            specified = false;
                        } else if (scriptAttr.test(name)) {
                            specified = !!options.scripts;
                        }

                        if (specified) {
                            specifiedAttributes.push(attribute);
                        }
                    }

                    if (!specifiedAttributes.length) {
                        return;
                    }

                    specifiedAttributes.sort(function (a, b) {
                        return a.nodeName > b.nodeName ? 1 : a.nodeName < b.nodeName ? -1 : 0;
                    });

                    for (i = 0, l = specifiedAttributes.length; i < l; i++) {
                        attribute = specifiedAttributes[i];
                        name = attribute.nodeName;
                        value = attribute.nodeValue;

                        if (name == "class" && value == "k-table") {
                            continue;
                        }

                        name = name.replace(scriptAttr, "");

                        result.push(' ');
                        result.push(name);
                        result.push('="');

                        if (name == 'style') {
                            styleAttr(value || node.style.cssText);
                        } else if (name == 'src' || name == 'href') {
                            result.push(kendo.htmlEncode(node.getAttribute(name, 2)));
                        } else {
                            result.push(dom.fillAttrs[name] ? name : value);
                        }

                        result.push('"');
                    }
                }

                function children(node, skip, skipEncoding) {
                    for (var childNode = node.firstChild; childNode; childNode = childNode.nextSibling) {
                        child(childNode, skip, skipEncoding);
                    }
                }

                function text(node) {
                    return node.nodeValue.replace(/\ufeff/g, "");
                }

                function child(node, skip, skipEncoding) {
                    var nodeType = node.nodeType,
                tagName, mapper,
                parent, value, previous;

                    if (nodeType == 1) {
                        tagName = dom.name(node);

                        if (!tagName || dom.insignificant(node)) {
                            return;
                        }

                        if (dom.isInline(node) && node.childNodes.length == 1 && node.firstChild.nodeType == 3 && !text(node.firstChild)) {
                            return;
                        }

                        if (!options.scripts && tagName == "telerik:script") {
                            return;
                        }

                        mapper = tagMap[tagName];

                        if (mapper) {
                            mapper.start(node);
                            children(node, false, mapper.skipEncoding);
                            mapper.end(node);
                            return;
                        }

                        result.push('<');
                        result.push(tagName);

                        attr(node);

                        if (dom.empty[tagName]) {
                            result.push(' />');
                        } else {
                            result.push('>');
                            children(node, skip || dom.is(node, 'pre'));
                            result.push('</');
                            result.push(tagName);
                            result.push('>');
                        }
                    } else if (nodeType == 3) {
                        value = text(node);

                        if (!skip && supportsLeadingWhitespace) {
                            parent = node.parentNode;
                            previous = node.previousSibling;

                            if (!previous) {
                                previous = (dom.isInline(parent) ? parent : node).previousSibling;
                            }

                            if (!previous || previous.innerHTML === "" || dom.isBlock(previous)) {
                                value = value.replace(/^[\r\n\v\f\t ]+/, '');
                            }

                            value = value.replace(/ +/, ' ');
                        }

                        result.push(skipEncoding ? value : dom.encode(value, options));

                    } else if (nodeType == 4) {
                        result.push('<![CDATA[');
                        result.push(node.data);
                        result.push(']]>');
                    } else if (nodeType == 8) {
                        if (node.data.indexOf('[CDATA[') < 0) {
                            result.push('<!--');
                            result.push(node.data);
                            result.push('-->');
                        } else {
                            result.push('<!');
                            result.push(node.data);
                            result.push('>');
                        }
                    }
                }

                function textOnly(root) {
                    var childrenCount = root.childNodes.length;
                    var textChild = childrenCount && root.firstChild.nodeType == 3;

                    return textChild && (childrenCount == 1 || (childrenCount == 2 && dom.insignificant(root.lastChild)));
                }

                if (textOnly(root)) {
                    return dom.encode(text(root.firstChild).replace(/[\r\n\v\f\t ]+/, ' '), options);
                }

                children(root);

                result = result.join('');

                // if serialized dom contains only whitespace elements, consider it empty (required field validation)
                if (result.replace(brRe, "").replace(emptyPRe, "") === "") {
                    return "";
                }

                return result;
            }

        };

        extend(Editor, {
            Serializer: Serializer
        });

    })(window.kendo.jQuery);

    (function ($) {

        // Imports ================================================================
        var kendo = window.kendo,
        Class = kendo.Class,
        extend = $.extend,
        Editor = kendo.ui.editor,
        browser = kendo.support.browser,
        dom = Editor.Dom,
        findNodeIndex = dom.findNodeIndex,
        isDataNode = dom.isDataNode,
        findClosestAncestor = dom.findClosestAncestor,
        getNodeLength = dom.getNodeLength,
        normalize = dom.normalize;

        var SelectionUtils = {
            selectionFromWindow: function (window) {
                if (!("getSelection" in window)) {
                    return new W3CSelection(window.document);
                }

                return window.getSelection();
            },

            selectionFromRange: function (range) {
                var rangeDocument = RangeUtils.documentFromRange(range);
                return SelectionUtils.selectionFromDocument(rangeDocument);
            },

            selectionFromDocument: function (document) {
                return SelectionUtils.selectionFromWindow(dom.windowFromDocument(document));
            }
        };

        var W3CRange = Class.extend({
            init: function (doc) {
                $.extend(this, {
                    ownerDocument: doc, /* not part of the spec; used when cloning ranges, traversing the dom and creating fragments */
                    startContainer: doc,
                    endContainer: doc,
                    commonAncestorContainer: doc,
                    startOffset: 0,
                    endOffset: 0,
                    collapsed: true
                });
            },

            // Positioning Methods
            setStart: function (node, offset) {
                this.startContainer = node;
                this.startOffset = offset;
                updateRangeProperties(this);
                fixIvalidRange(this, true);
            },

            setEnd: function (node, offset) {
                this.endContainer = node;
                this.endOffset = offset;
                updateRangeProperties(this);
                fixIvalidRange(this, false);
            },

            setStartBefore: function (node) {
                this.setStart(node.parentNode, findNodeIndex(node));
            },

            setStartAfter: function (node) {
                this.setStart(node.parentNode, findNodeIndex(node) + 1);
            },

            setEndBefore: function (node) {
                this.setEnd(node.parentNode, findNodeIndex(node));
            },

            setEndAfter: function (node) {
                this.setEnd(node.parentNode, findNodeIndex(node) + 1);
            },

            selectNode: function (node) {
                this.setStartBefore(node);
                this.setEndAfter(node);
            },

            selectNodeContents: function (node) {
                this.setStart(node, 0);
                this.setEnd(node, node[node.nodeType === 1 ? 'childNodes' : 'nodeValue'].length);
            },

            collapse: function (toStart) {
                var that = this;

                if (toStart) {
                    that.setEnd(that.startContainer, that.startOffset);
                } else {
                    that.setStart(that.endContainer, that.endOffset);
                }
            },

            // Editing Methods

            deleteContents: function () {
                var that = this,
            range = that.cloneRange();

                if (that.startContainer != that.commonAncestorContainer) {
                    that.setStartAfter(findClosestAncestor(that.commonAncestorContainer, that.startContainer));
                }

                that.collapse(true);

                (function deleteSubtree(iterator) {
                    while (iterator.next()) {
                        if (iterator.hasPartialSubtree()) {
                            deleteSubtree(iterator.getSubtreeIterator());
                        } else {
                            iterator.remove();
                        }
                    }
                })(new RangeIterator(range));
            },

            cloneContents: function () {
                // clone subtree
                var document = RangeUtils.documentFromRange(this);
                return (function cloneSubtree(iterator) {
                    var node, frag = document.createDocumentFragment();

                    while (node = iterator.next()) {
                        node = node.cloneNode(!iterator.hasPartialSubtree());

                        if (iterator.hasPartialSubtree()) {
                            node.appendChild(cloneSubtree(iterator.getSubtreeIterator()));
                        }

                        frag.appendChild(node);
                    }

                    return frag;
                })(new RangeIterator(this));
            },

            extractContents: function () {
                var that = this,
            range = that.cloneRange();

                if (that.startContainer != that.commonAncestorContainer) {
                    that.setStartAfter(findClosestAncestor(that.commonAncestorContainer, that.startContainer));
                }

                that.collapse(true);

                var document = RangeUtils.documentFromRange(that);

                return (function extractSubtree(iterator) {
                    var node, frag = document.createDocumentFragment();

                    while (node = iterator.next()) {
                        if (iterator.hasPartialSubtree()) {
                            node = node.cloneNode(false);
                            node.appendChild(extractSubtree(iterator.getSubtreeIterator()));
                        } else {
                            iterator.remove(that.originalRange);
                        }

                        frag.appendChild(node);
                    }

                    return frag;
                })(new RangeIterator(range));
            },

            insertNode: function (node) {
                var that = this;

                if (isDataNode(that.startContainer)) {
                    if (that.startOffset != that.startContainer.nodeValue.length) {
                        dom.splitDataNode(that.startContainer, that.startOffset);
                    }

                    dom.insertAfter(node, that.startContainer);
                } else {
                    dom.insertAt(that.startContainer, node, that.startOffset);
                }

                that.setStart(that.startContainer, that.startOffset);
            },

            cloneRange: function () {
                // fast copy
                return $.extend(new W3CRange(this.ownerDocument), {
                    startContainer: this.startContainer,
                    endContainer: this.endContainer,
                    commonAncestorContainer: this.commonAncestorContainer,
                    startOffset: this.startOffset,
                    endOffset: this.endOffset,
                    collapsed: this.collapsed,

                    originalRange: this /* not part of the spec; used to update the original range when calling extractContents() on clones */
                });
            },

            // used for debug purposes
            toString: function () {
                var startNodeName = this.startContainer.nodeName,
            endNodeName = this.endContainer.nodeName;

                return [startNodeName == "#text" ? this.startContainer.nodeValue : startNodeName, '(', this.startOffset, ') : ',
                endNodeName == "#text" ? this.endContainer.nodeValue : endNodeName, '(', this.endOffset, ')'].join('');
            }
        });

        /* can be used in Range.compareBoundaryPoints if we need it one day */
        function compareBoundaries(start, end, startOffset, endOffset) {
            if (start == end) {
                return endOffset - startOffset;
            }

            // end is child of start
            var container = end;
            while (container && container.parentNode != start) {
                container = container.parentNode;
            }

            if (container) {
                return findNodeIndex(container) - startOffset;
            }

            // start is child of end
            container = start;
            while (container && container.parentNode != end) {
                container = container.parentNode;
            }

            if (container) {
                return endOffset - findNodeIndex(container) - 1;
            }

            // deep traversal
            var root = dom.commonAncestor(start, end);
            var startAncestor = start;

            while (startAncestor && startAncestor.parentNode != root) {
                startAncestor = startAncestor.parentNode;
            }

            if (!startAncestor) {
                startAncestor = root;
            }

            var endAncestor = end;
            while (endAncestor && endAncestor.parentNode != root) {
                endAncestor = endAncestor.parentNode;
            }

            if (!endAncestor) {
                endAncestor = root;
            }

            if (startAncestor == endAncestor) {
                return 0;
            }

            return findNodeIndex(endAncestor) - findNodeIndex(startAncestor);
        }

        function fixIvalidRange(range, toStart) {
            function isInvalidRange(range) {
                try {
                    return compareBoundaries(range.startContainer, range.endContainer, range.startOffset, range.endOffset) < 0;
                } catch (ex) {
                    // range was initially invalid (e.g. when cloned from invalid range) - it must be fixed
                    return true;
                }
            }

            if (isInvalidRange(range)) {
                if (toStart) {
                    range.commonAncestorContainer = range.endContainer = range.startContainer;
                    range.endOffset = range.startOffset;
                } else {
                    range.commonAncestorContainer = range.startContainer = range.endContainer;
                    range.startOffset = range.endOffset;
                }

                range.collapsed = true;
            }
        }

        function updateRangeProperties(range) {
            range.collapsed = range.startContainer == range.endContainer && range.startOffset == range.endOffset;

            var node = range.startContainer;
            while (node && node != range.endContainer && !dom.isAncestorOf(node, range.endContainer)) {
                node = node.parentNode;
            }

            range.commonAncestorContainer = node;
        }

        var RangeIterator = Class.extend({
            init: function (range) {
                $.extend(this, {
                    range: range,
                    _current: null,
                    _next: null,
                    _end: null
                });

                if (range.collapsed) {
                    return;
                }

                var root = range.commonAncestorContainer;

                this._next = range.startContainer == root && !isDataNode(range.startContainer) ?
        range.startContainer.childNodes[range.startOffset] :
        findClosestAncestor(root, range.startContainer);

                this._end = range.endContainer == root && !isDataNode(range.endContainer) ?
        range.endContainer.childNodes[range.endOffset] :
        findClosestAncestor(root, range.endContainer).nextSibling;
            },

            hasNext: function () {
                return !!this._next;
            },

            next: function () {
                var that = this,
            current = that._current = that._next;
                that._next = that._current && that._current.nextSibling != that._end ?
        that._current.nextSibling : null;

                if (isDataNode(that._current)) {
                    if (that.range.endContainer == that._current) {
                        current = current.cloneNode(true);
                        current.deleteData(that.range.endOffset, current.length - that.range.endOffset);
                    }

                    if (that.range.startContainer == that._current) {
                        current = current.cloneNode(true);
                        current.deleteData(0, that.range.startOffset);
                    }
                }

                return current;
            },

            traverse: function (callback) {
                var that = this,
            current;

                function next() {
                    that._current = that._next;
                    that._next = that._current && that._current.nextSibling != that._end ? that._current.nextSibling : null;
                    return that._current;
                }

                while (current = next()) {
                    if (that.hasPartialSubtree()) {
                        that.getSubtreeIterator().traverse(callback);
                    } else {
                        callback(current);
                    }
                }

                return current;
            },

            remove: function (originalRange) {
                var that = this,
            inStartContainer = that.range.startContainer == that._current,
            inEndContainer = that.range.endContainer == that._current,
            start, end, delta;

                if (isDataNode(that._current) && (inStartContainer || inEndContainer)) {
                    start = inStartContainer ? that.range.startOffset : 0;
                    end = inEndContainer ? that.range.endOffset : that._current.length;
                    delta = end - start;

                    if (originalRange && (inStartContainer || inEndContainer)) {
                        if (that._current == originalRange.startContainer && start <= originalRange.startOffset) {
                            originalRange.startOffset -= delta;
                        }

                        if (that._current == originalRange.endContainer && end <= originalRange.endOffset) {
                            originalRange.endOffset -= delta;
                        }
                    }

                    that._current.deleteData(start, delta);
                } else {
                    var parent = that._current.parentNode;

                    if (originalRange && (that.range.startContainer == parent || that.range.endContainer == parent)) {
                        var nodeIndex = findNodeIndex(that._current);

                        if (parent == originalRange.startContainer && nodeIndex <= originalRange.startOffset) {
                            originalRange.startOffset -= 1;
                        }

                        if (parent == originalRange.endContainer && nodeIndex < originalRange.endOffset) {
                            originalRange.endOffset -= 1;
                        }
                    }

                    dom.remove(that._current);
                }
            },

            hasPartialSubtree: function () {
                return !isDataNode(this._current) &&
        (dom.isAncestorOrSelf(this._current, this.range.startContainer) ||
            dom.isAncestorOrSelf(this._current, this.range.endContainer));
            },

            getSubtreeIterator: function () {
                var that = this,
            subRange = that.range.cloneRange();

                subRange.selectNodeContents(that._current);

                if (dom.isAncestorOrSelf(that._current, that.range.startContainer)) {
                    subRange.setStart(that.range.startContainer, that.range.startOffset);
                }

                if (dom.isAncestorOrSelf(that._current, that.range.endContainer)) {
                    subRange.setEnd(that.range.endContainer, that.range.endOffset);
                }

                return new RangeIterator(subRange);
            }
        });

        var W3CSelection = Class.extend({
            init: function (doc) {
                this.ownerDocument = doc;
                this.rangeCount = 1;
            },

            addRange: function (range) {
                var textRange = this.ownerDocument.body.createTextRange();

                // end container should be adopted first in order to prevent selection with negative length
                adoptContainer(textRange, range, false);
                adoptContainer(textRange, range, true);

                textRange.select();
            },

            removeAllRanges: function () {
                var selection = this.ownerDocument.selection;

                if (selection.type != "None") {
                    selection.empty();
                }
            },

            getRangeAt: function () {
                var textRange,
            range = new W3CRange(this.ownerDocument),
            selection = this.ownerDocument.selection,
            element, commonAncestor;

                try {
                    textRange = selection.createRange();
                    element = textRange.item ? textRange.item(0) : textRange.parentElement();
                    if (element.ownerDocument != this.ownerDocument) {
                        return range;
                    }
                } catch (ex) {
                    return range;
                }

                if (selection.type == "Control") {
                    range.selectNode(textRange.item(0));
                } else {
                    commonAncestor = textRangeContainer(textRange);
                    adoptEndPoint(textRange, range, commonAncestor, true);
                    adoptEndPoint(textRange, range, commonAncestor, false);

                    if (range.startContainer.nodeType == 9) {
                        range.setStart(range.endContainer, range.startOffset);
                    }

                    if (range.endContainer.nodeType == 9) {
                        range.setEnd(range.startContainer, range.endOffset);
                    }

                    if (textRange.compareEndPoints("StartToEnd", textRange) === 0) {
                        range.collapse(false);
                    }

                    var startContainer = range.startContainer,
                endContainer = range.endContainer,
                body = this.ownerDocument.body;

                    if (!range.collapsed && range.startOffset === 0 && range.endOffset == getNodeLength(range.endContainer) &&  // check for full body selection
                !(startContainer == endContainer && isDataNode(startContainer) && startContainer.parentNode == body)) { // but not when single textnode is selected
                        var movedStart = false,
                    movedEnd = false;

                        while (findNodeIndex(startContainer) === 0 && startContainer == startContainer.parentNode.firstChild && startContainer != body) {
                            startContainer = startContainer.parentNode;
                            movedStart = true;
                        }

                        while (findNodeIndex(endContainer) == getNodeLength(endContainer.parentNode) - 1 && endContainer == endContainer.parentNode.lastChild && endContainer != body) {
                            endContainer = endContainer.parentNode;
                            movedEnd = true;
                        }

                        if (startContainer == body && endContainer == body && movedStart && movedEnd) {
                            range.setStart(startContainer, 0);
                            range.setEnd(endContainer, getNodeLength(body));
                        }
                    }
                }

                return range;
            }
        });

        function textRangeContainer(textRange) {
            var left = textRange.duplicate(),
        right = textRange.duplicate();

            left.collapse(true);
            right.collapse(false);

            return dom.commonAncestor(textRange.parentElement(), left.parentElement(), right.parentElement());
        }

        function adoptContainer(textRange, range, start) {
            // find anchor node and offset
            var container = range[start ? "startContainer" : "endContainer"],
        offset = range[start ? "startOffset" : "endOffset"],
        textOffset = 0,
        isData = isDataNode(container),
        anchorNode = isData ? container : container.childNodes[offset] || null,
        anchorParent = isData ? container.parentNode : container,
        doc = range.ownerDocument,
        cursor = doc.body.createTextRange(),
        cursorNode;

            // visible data nodes need a text offset
            if (container.nodeType == 3 || container.nodeType == 4) {
                textOffset = offset;
            }

            if (!anchorParent) {
                anchorParent = doc.body;
            }

            if (anchorParent.nodeName.toLowerCase() == "img") {
                cursor.moveToElementText(anchorParent);
                cursor.collapse(false);
                textRange.setEndPoint(start ? "StartToStart" : "EndToStart", cursor);
            } else {
                // create a cursor element node to position range (since we can't select text nodes)
                cursorNode = anchorParent.insertBefore(dom.create(doc, "a"), anchorNode);

                cursor.moveToElementText(cursorNode);
                dom.remove(cursorNode);
                cursor[start ? "moveStart" : "moveEnd"]("character", textOffset);
                cursor.collapse(false);
                textRange.setEndPoint(start ? "StartToStart" : "EndToStart", cursor);
            }
        }

        function adoptEndPoint(textRange, range, commonAncestor, start) {
            var cursorNode = dom.create(range.ownerDocument, "a"),
        cursor = textRange.duplicate(),
        comparison = start ? "StartToStart" : "StartToEnd",
        result, parent, target,
        previous, next,
        args, index,
        appended = false;

            cursorNode.innerHTML = "\ufeff";
            cursor.collapse(start);

            parent = cursor.parentElement();

            if (!dom.isAncestorOrSelf(commonAncestor, parent)) {
                parent = commonAncestor;
            }

            // detect range end points
            // insert cursorNode within the textRange parent and move the cursor until it gets outside of the textRange
            do {
                if (appended) {
                    parent.insertBefore(cursorNode, cursorNode.previousSibling);
                } else {
                    parent.appendChild(cursorNode);
                    appended = true;
                }
                cursor.moveToElementText(cursorNode);
            } while ((result = cursor.compareEndPoints(comparison, textRange)) > 0 && cursorNode.previousSibling);

            target = cursorNode.nextSibling;

            if (result == -1 && isDataNode(target)) {
                cursor.setEndPoint(start ? "EndToStart" : "EndToEnd", textRange);

                dom.remove(cursorNode);

                args = [target, cursor.text.length];
            } else {
                previous = !start && cursorNode.previousSibling;
                next = start && cursorNode.nextSibling;

                if (isDataNode(next)) {
                    args = [next, 0];
                } else if (isDataNode(previous)) {
                    args = [previous, previous.length];
                } else {
                    index = findNodeIndex(cursorNode);

                    if (parent.nextSibling && index == parent.childNodes.length - 1) {
                        args = [parent.nextSibling, 0];
                    } else {
                        args = [parent, index];
                    }
                }

                dom.remove(cursorNode);
            }

            range[start ? "setStart" : "setEnd"].apply(range, args);
        }

        var RangeEnumerator = Class.extend({
            init: function (range) {
                this.enumerate = function () {
                    var nodes = [];

                    function visit(node) {
                        if (dom.is(node, "img") || (node.nodeType == 3 && (!dom.isWhitespace(node) || node.nodeValue == "\ufeff"))) {
                            nodes.push(node);
                        } else {
                            node = node.firstChild;
                            while (node) {
                                visit(node);
                                node = node.nextSibling;
                            }
                        }
                    }

                    new RangeIterator(range).traverse(visit);

                    return nodes;
                };
            }
        });

        var RestorePoint = Class.extend({
            init: function (range, body) {
                var that = this;
                that.range = range;
                that.rootNode = RangeUtils.documentFromRange(range);
                that.body = body || that.getEditable(range);
                if (dom.name(that.body) != "body") {
                    that.rootNode = that.body;
                }
                that.html = that.body.innerHTML;

                that.startContainer = that.nodeToPath(range.startContainer);
                that.endContainer = that.nodeToPath(range.endContainer);
                that.startOffset = that.offset(range.startContainer, range.startOffset);
                that.endOffset = that.offset(range.endContainer, range.endOffset);
            },

            index: function (node) {
                var result = 0,
            lastType = node.nodeType;

                while (node = node.previousSibling) {
                    var nodeType = node.nodeType;

                    if (nodeType != 3 || lastType != nodeType) {
                        result++;
                    }

                    lastType = nodeType;
                }

                return result;
            },

            getEditable: function (range) {
                var root = range.commonAncestorContainer;

                while (root && (root.nodeType == 3 || root.attributes && !root.attributes.contentEditable)) {
                    root = root.parentNode;
                }

                return root;
            },

            restoreHtml: function () {
                this.body.innerHTML = this.html;
            },

            offset: function (node, value) {
                if (node.nodeType == 3) {
                    while ((node = node.previousSibling) && node.nodeType == 3) {
                        value += node.nodeValue.length;
                    }
                }

                return value;
            },

            nodeToPath: function (node) {
                var path = [];

                while (node != this.rootNode) {
                    path.push(this.index(node));
                    node = node.parentNode;
                }

                return path;
            },

            toRangePoint: function (range, start, path, denormalizedOffset) {
                var node = this.rootNode,
            length = path.length,
            offset = denormalizedOffset;

                while (length--) {
                    node = node.childNodes[path[length]];
                }

                while (node && node.nodeType == 3 && node.nodeValue.length < offset) {
                    offset -= node.nodeValue.length;
                    node = node.nextSibling;
                }

                if (node && offset >= 0) {
                    range[start ? 'setStart' : 'setEnd'](node, offset);
                }
            },

            toRange: function () {
                var that = this,
            result = that.range.cloneRange();

                that.toRangePoint(result, true, that.startContainer, that.startOffset);
                that.toRangePoint(result, false, that.endContainer, that.endOffset);

                return result;
            }

        });

        var Marker = Class.extend({
            init: function () {
                this.caret = null;
            },

            addCaret: function (range) {
                var that = this;

                that.caret = dom.create(RangeUtils.documentFromRange(range), 'span', { className: 'k-marker' });
                range.insertNode(that.caret);
                range.selectNode(that.caret);
                return that.caret;
            },

            removeCaret: function (range) {
                var that = this,
            previous = that.caret.previousSibling,
            startOffset = 0;

                if (previous) {
                    startOffset = isDataNode(previous) ? previous.nodeValue.length : findNodeIndex(previous);
                }

                var container = that.caret.parentNode;
                var containerIndex = previous ? findNodeIndex(previous) : 0;

                dom.remove(that.caret);
                normalize(container);

                var node = container.childNodes[containerIndex];

                if (isDataNode(node)) {
                    range.setStart(node, startOffset);
                } else if (node) {
                    var textNode = dom.lastTextNode(node);
                    if (textNode) {
                        range.setStart(textNode, textNode.nodeValue.length);
                    } else {
                        range[previous ? 'setStartAfter' : 'setStartBefore'](node);
                    }
                } else {
                    if (!browser.msie && !container.innerHTML) {
                        container.innerHTML = '<br _moz_dirty="" />';
                    }

                    range.selectNodeContents(container);
                }
                range.collapse(true);
            },

            add: function (range, expand) {
                var that = this;

                var collapsed = range.collapsed && !RangeUtils.isExpandable(range);
                var doc = RangeUtils.documentFromRange(range);

                if (expand && range.collapsed) {
                    that.addCaret(range);
                    range = RangeUtils.expand(range);
                }

                var rangeBoundary = range.cloneRange();

                rangeBoundary.collapse(false);
                that.end = dom.create(doc, 'span', { className: 'k-marker' });
                rangeBoundary.insertNode(that.end);

                rangeBoundary = range.cloneRange();
                rangeBoundary.collapse(true);
                that.start = that.end.cloneNode(true);
                rangeBoundary.insertNode(that.start);

                that._removeDeadMarkers(that.start, that.end);

                if (collapsed) {
                    var bom = doc.createTextNode("\ufeff");
                    dom.insertAfter(bom.cloneNode(), that.start);
                    dom.insertBefore(bom, that.end);
                }

                range.setStartBefore(that.start);
                range.setEndAfter(that.end);

                normalize(range.commonAncestorContainer);

                return range;
            },

            _removeDeadMarkers: function (start, end) {
                if (start.previousSibling && start.previousSibling.nodeValue == "\ufeff") {
                    dom.remove(start.previousSibling);
                }

                if (end.nextSibling && end.nextSibling.nodeValue == "\ufeff") {
                    dom.remove(end.nextSibling);
                }
            },

            remove: function (range) {
                var that = this,
            start = that.start,
            end = that.end,
            shouldNormalizeStart,
            shouldNormalizeEnd,
            shouldNormalize;

                normalize(range.commonAncestorContainer);

                while (!start.nextSibling && start.parentNode) {
                    start = start.parentNode;
                }

                while (!end.previousSibling && end.parentNode) {
                    end = end.parentNode;
                }

                // merely accessing the siblings will solve range issues in IE
                shouldNormalizeStart = (start.previousSibling && start.previousSibling.nodeType == 3) &&
                               (start.nextSibling && start.nextSibling.nodeType == 3);

                shouldNormalizeEnd = (end.previousSibling && end.previousSibling.nodeType == 3) &&
                             (end.nextSibling && end.nextSibling.nodeType == 3);

                shouldNormalize = shouldNormalizeStart && shouldNormalizeEnd;

                start = start.nextSibling;
                end = end.previousSibling;

                var collapsed = false;
                var collapsedToStart = false;
                // collapsed range
                if (start == that.end) {
                    collapsedToStart = !!that.start.previousSibling;
                    start = end = that.start.previousSibling || that.end.nextSibling;
                    collapsed = true;
                }

                dom.remove(that.start);
                dom.remove(that.end);

                if (!start || !end) {
                    range.selectNodeContents(range.commonAncestorContainer);
                    range.collapse(true);
                    return;
                }

                var startOffset = collapsed ? isDataNode(start) ? start.nodeValue.length : start.childNodes.length : 0;
                var endOffset = isDataNode(end) ? end.nodeValue.length : end.childNodes.length;

                if (start.nodeType == 3) {
                    while (start.previousSibling && start.previousSibling.nodeType == 3) {
                        start = start.previousSibling;
                        startOffset += start.nodeValue.length;
                    }
                }

                if (end.nodeType == 3) {
                    while (end.previousSibling && end.previousSibling.nodeType == 3) {
                        end = end.previousSibling;
                        endOffset += end.nodeValue.length;
                    }
                }

                var startIndex = findNodeIndex(start), startParent = start.parentNode;
                var endIndex = findNodeIndex(end), endParent = end.parentNode;

                for (var startPointer = start; startPointer.previousSibling; startPointer = startPointer.previousSibling) {
                    if (startPointer.nodeType == 3 && startPointer.previousSibling.nodeType == 3) {
                        startIndex--;
                    }
                }

                for (var endPointer = end; endPointer.previousSibling; endPointer = endPointer.previousSibling) {
                    if (endPointer.nodeType == 3 && endPointer.previousSibling.nodeType == 3) {
                        endIndex--;
                    }
                }

                normalize(startParent);

                if (start.nodeType == 3) {
                    start = startParent.childNodes[startIndex];
                }

                normalize(endParent);
                if (end.nodeType == 3) {
                    end = endParent.childNodes[endIndex];
                }

                if (collapsed) {
                    if (start.nodeType == 3) {
                        range.setStart(start, startOffset);
                    } else {
                        range[collapsedToStart ? 'setStartAfter' : 'setStartBefore'](start);
                    }

                    range.collapse(true);

                } else {
                    if (start.nodeType == 3) {
                        range.setStart(start, startOffset);
                    } else {
                        range.setStartBefore(start);
                    }

                    if (end.nodeType == 3) {
                        range.setEnd(end, endOffset);
                    } else {
                        range.setEndAfter(end);
                    }
                }
                if (that.caret) {
                    that.removeCaret(range);
                }
            }

        });

        var boundary = /[\u0009-\u000d]|\u0020|\u00a0|\ufeff|\.|,|;|:|!|\(|\)|\?/;

        var RangeUtils = {
            nodes: function (range) {
                var nodes = RangeUtils.textNodes(range);
                if (!nodes.length) {
                    range.selectNodeContents(range.commonAncestorContainer);
                    nodes = RangeUtils.textNodes(range);
                    if (!nodes.length) {
                        nodes = dom.significantChildNodes(range.commonAncestorContainer);
                    }
                }
                return nodes;
            },

            textNodes: function (range) {
                return new RangeEnumerator(range).enumerate();
            },

            documentFromRange: function (range) {
                var startContainer = range.startContainer;
                return startContainer.nodeType == 9 ? startContainer : startContainer.ownerDocument;
            },

            createRange: function (document) {
                if (browser.msie && browser.version < 9) {
                    return new W3CRange(document);
                }

                return document.createRange();
            },

            selectRange: function (range) {
                var image = RangeUtils.image(range);
                if (image) {
                    range.setStartAfter(image);
                    range.setEndAfter(image);
                }
                var selection = SelectionUtils.selectionFromRange(range);
                selection.removeAllRanges();
                selection.addRange(range);
            },

            stringify: function (range) {
                return kendo.format(
            "{0}:{1} - {2}:{3}",
            dom.name(range.startContainer), range.startOffset,
            dom.name(range.endContainer), range.endOffset
        );
            },

            split: function (range, node, trim) {
                function partition(start) {
                    var partitionRange = range.cloneRange();
                    partitionRange.collapse(start);
                    partitionRange[start ? 'setStartBefore' : 'setEndAfter'](node);
                    var contents = partitionRange.extractContents();
                    if (trim) {
                        contents = dom.trim(contents);
                    }
                    dom[start ? 'insertBefore' : 'insertAfter'](contents, node);
                }
                partition(true);
                partition(false);
            },

            mapAll: function (range, map) {
                var nodes = [];

                new RangeIterator(range).traverse(function (node) {
                    var mapped = map(node);

                    if (mapped && $.inArray(mapped, nodes) < 0) {
                        nodes.push(mapped);
                    }
                });

                return nodes;
            },

            getAll: function (range, predicate) {
                var selector = predicate;

                if (typeof predicate == "string") {
                    predicate = function (node) {
                        return dom.is(node, selector);
                    };
                }

                return RangeUtils.mapAll(range, function (node) {
                    if (predicate(node)) {
                        return node;
                    }
                });
            },

            getMarkers: function (range) {
                return RangeUtils.getAll(range, function (node) {
                    return node.className == 'k-marker';
                });
            },

            image: function (range) {
                var nodes = RangeUtils.getAll(range, "img");

                if (nodes.length == 1) {
                    return nodes[0];
                }
            },

            wrapSelectedElements: function (range) {
                var startEditable = dom.editableParent(range.startContainer);
                var endEditable = dom.editableParent(range.endContainer);

                while (range.startOffset === 0 && range.startContainer != startEditable) {
                    range.setStart(range.startContainer.parentNode, dom.findNodeIndex(range.startContainer));
                }

                function isEnd(offset, container) {
                    var length = dom.getNodeLength(container);

                    if (offset == length) {
                        return true;
                    }

                    for (var i = offset; i < length; i++) {
                        if (!dom.insignificant(container.childNodes[i])) {
                            return false;
                        }
                    }

                    return true;
                }

                while (isEnd(range.endOffset, range.endContainer) && range.endContainer != endEditable) {
                    range.setEnd(range.endContainer.parentNode, dom.findNodeIndex(range.endContainer) + 1);
                }

                return range;
            },

            expand: function (range) {
                var result = range.cloneRange();

                var startContainer = result.startContainer.childNodes[result.startOffset === 0 ? 0 : result.startOffset - 1];
                var endContainer = result.endContainer.childNodes[result.endOffset];

                if (!isDataNode(startContainer) || !isDataNode(endContainer)) {
                    return result;
                }

                var beforeCaret = startContainer.nodeValue;
                var afterCaret = endContainer.nodeValue;

                if (!beforeCaret || !afterCaret) {
                    return result;
                }

                var startOffset = beforeCaret.split('').reverse().join('').search(boundary);
                var endOffset = afterCaret.search(boundary);

                if (!startOffset || !endOffset) {
                    return result;
                }

                endOffset = endOffset == -1 ? afterCaret.length : endOffset;
                startOffset = startOffset == -1 ? 0 : beforeCaret.length - startOffset;

                result.setStart(startContainer, startOffset);
                result.setEnd(endContainer, endOffset);

                return result;
            },

            isExpandable: function (range) {
                var node = range.startContainer;
                var rangeDocument = RangeUtils.documentFromRange(range);

                if (node == rangeDocument || node == rangeDocument.body) {
                    return false;
                }

                var result = range.cloneRange();

                var value = node.nodeValue;
                if (!value) {
                    return false;
                }

                var beforeCaret = value.substring(0, result.startOffset);
                var afterCaret = value.substring(result.startOffset);

                var startOffset = 0, endOffset = 0;

                if (beforeCaret) {
                    startOffset = beforeCaret.split('').reverse().join('').search(boundary);
                }

                if (afterCaret) {
                    endOffset = afterCaret.search(boundary);
                }

                return startOffset && endOffset;
            }
        };

        extend(Editor, {
            SelectionUtils: SelectionUtils,
            W3CRange: W3CRange,
            RangeIterator: RangeIterator,
            W3CSelection: W3CSelection,
            RangeEnumerator: RangeEnumerator,
            RestorePoint: RestorePoint,
            Marker: Marker,
            RangeUtils: RangeUtils
        });

    })(window.kendo.jQuery);

    (function ($) {

        // Imports ================================================================
        var kendo = window.kendo,
        Class = kendo.Class,
        editorNS = kendo.ui.editor,
        EditorUtils = editorNS.EditorUtils,
        registerTool = EditorUtils.registerTool,
        dom = editorNS.Dom,
        Tool = editorNS.Tool,
        ToolTemplate = editorNS.ToolTemplate,
        RestorePoint = editorNS.RestorePoint,
        Marker = editorNS.Marker,
        extend = $.extend;

        function finishUpdate(editor, startRestorePoint) {
            var endRestorePoint = editor.selectionRestorePoint = new RestorePoint(editor.getRange());
            var command = new GenericCommand(startRestorePoint, endRestorePoint);
            command.editor = editor;

            editor.undoRedoStack.push(command);

            return endRestorePoint;
        }

        var Command = Class.extend({
            init: function (options) {
                this.options = options;
                this.restorePoint = new RestorePoint(options.range);
                this.marker = new Marker();
                this.formatter = options.formatter;
            },

            getRange: function () {
                return this.restorePoint.toRange();
            },

            lockRange: function (expand) {
                return this.marker.add(this.getRange(), expand);
            },

            releaseRange: function (range) {
                this.marker.remove(range);
                this.editor.selectRange(range);
            },

            undo: function () {
                var point = this.restorePoint;
                point.restoreHtml();
                this.editor.selectRange(point.toRange());
            },

            redo: function () {
                this.exec();
            },

            createDialog: function (content, options) {
                var editor = this.editor;

                return $(content).appendTo(document.body)
            .kendoWindow(extend({}, editor.options.dialogOptions, options))
            .closest(".k-window").toggleClass("k-rtl", kendo.support.isRtl(editor.wrapper)).end();
            },

            exec: function () {
                var range = this.lockRange(true);
                this.formatter.editor = this.editor;
                this.formatter.toggle(range);
                this.releaseRange(range);
            }
        });

        var GenericCommand = Class.extend({
            init: function (startRestorePoint, endRestorePoint) {
                this.body = startRestorePoint.body;
                this.startRestorePoint = startRestorePoint;
                this.endRestorePoint = endRestorePoint;
            },

            redo: function () {
                this.body.innerHTML = this.endRestorePoint.html;
                this.editor.selectRange(this.endRestorePoint.toRange());
            },

            undo: function () {
                this.body.innerHTML = this.startRestorePoint.html;
                this.editor.selectRange(this.startRestorePoint.toRange());
            }
        });

        var InsertHtmlCommand = Command.extend({
            init: function (options) {
                Command.fn.init.call(this, options);

                this.managesUndoRedo = true;
            },

            exec: function () {
                var editor = this.editor;
                var options = this.options;
                var range = options.range;
                var body = editor.body;
                var startRestorePoint = new RestorePoint(range, body);
                var html = options.html || options.value || '';

                editor.selectRange(range);

                editor.clipboard.paste(html, options);

                if (options.postProcess) {
                    options.postProcess(editor, editor.getRange());
                }

                var genericCommand = new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange(), body));
                genericCommand.editor = editor;
                editor.undoRedoStack.push(genericCommand);

                editor.focus();
            }
        });

        var InsertHtmlTool = Tool.extend({
            initialize: function (ui, initOptions) {
                var editor = initOptions.editor,
            options = this.options,
            dataSource = options.items ? options.items : editor.options.insertHtml;

                new editorNS.SelectBox(ui, {
                    dataSource: dataSource,
                    dataTextField: "text",
                    dataValueField: "value",
                    change: function () {
                        Tool.exec(editor, 'insertHtml', this.value());
                    },
                    title: editor.options.messages.insertHtml,
                    highlightFirst: false
                });
            },

            command: function (commandArguments) {
                return new InsertHtmlCommand(commandArguments);
            },

            update: function (ui) {
                var selectbox = ui.data("kendoSelectBox") || ui.find("select").data("kendoSelectBox");
                selectbox.close();
                selectbox.value(selectbox.options.title);
            }
        });

        var UndoRedoStack = Class.extend({
            init: function () {
                this.stack = [];
                this.currentCommandIndex = -1;
            },

            push: function (command) {
                this.stack = this.stack.slice(0, this.currentCommandIndex + 1);
                this.currentCommandIndex = this.stack.push(command) - 1;
            },

            undo: function () {
                if (this.canUndo()) {
                    this.stack[this.currentCommandIndex--].undo();
                }
            },

            redo: function () {
                if (this.canRedo()) {
                    this.stack[++this.currentCommandIndex].redo();
                }
            },

            canUndo: function () {
                return this.currentCommandIndex >= 0;
            },

            canRedo: function () {
                return this.currentCommandIndex != this.stack.length - 1;
            }
        });

        var TypingHandler = Class.extend({
            init: function (editor) {
                this.editor = editor;
            },

            keydown: function (e) {
                var that = this,
            editor = that.editor,
            keyboard = editor.keyboard,
            isTypingKey = keyboard.isTypingKey(e),
            evt = extend($.Event(), e);

                that.editor.trigger("keydown", evt);

                if (evt.isDefaultPrevented()) {
                    e.preventDefault();
                    return true;
                }

                if (!evt.isDefaultPrevented() && isTypingKey && !keyboard.isTypingInProgress()) {
                    var range = editor.getRange();
                    that.startRestorePoint = new RestorePoint(range);

                    keyboard.startTyping(function () {
                        that.endRestorePoint = finishUpdate(editor, that.startRestorePoint);
                    });

                    return true;
                }

                return false;
            },

            keyup: function (e) {
                var keyboard = this.editor.keyboard;

                this.editor.trigger("keyup", e);

                if (keyboard.isTypingInProgress()) {
                    keyboard.endTyping();
                    return true;
                }

                return false;
            }
        });

        var BackspaceHandler = Class.extend({
            init: function (editor) {
                this.editor = editor;
            },
            keydown: function (e) {
                if (e.keyCode === kendo.keys.BACKSPACE) {
                    var editor = this.editor;
                    var range = editor.getRange();
                    var emptyParagraphContent = kendo.support.browser.msie ? '' : '<br _moz_dirty="" />';

                    if (range.collapsed) {
                        return;
                    }

                    e.preventDefault();

                    var startRestorePoint = new RestorePoint(range);
                    var ancestor = range.commonAncestorContainer;

                    if (/t(able|body|r)/i.test(dom.name(ancestor))) {
                        range.selectNode(dom.closest(ancestor, "table"));
                    }

                    range.deleteContents();

                    ancestor = range.commonAncestorContainer;

                    if (dom.name(ancestor) === "p" && ancestor.innerHTML === "") {
                        ancestor.innerHTML = emptyParagraphContent;
                        range.setStart(ancestor, 0);
                        range.collapse(true);
                        editor.selectRange(range);
                    }

                    finishUpdate(editor, startRestorePoint);
                }
            },
            keyup: function () { }
        });

        var SystemHandler = Class.extend({
            init: function (editor) {
                this.editor = editor;
                this.systemCommandIsInProgress = false;
            },

            createUndoCommand: function () {
                this.startRestorePoint = this.endRestorePoint = finishUpdate(this.editor, this.startRestorePoint);
            },

            changed: function () {
                if (this.startRestorePoint) {
                    return this.startRestorePoint.html != this.editor.body.innerHTML;
                }

                return false;
            },

            keydown: function (e) {
                var that = this,
            editor = that.editor,
            keyboard = editor.keyboard;

                if (keyboard.isModifierKey(e)) {

                    if (keyboard.isTypingInProgress()) {
                        keyboard.endTyping(true);
                    }

                    that.startRestorePoint = new RestorePoint(editor.getRange());
                    return true;
                }

                if (keyboard.isSystem(e)) {
                    that.systemCommandIsInProgress = true;

                    if (that.changed()) {
                        that.systemCommandIsInProgress = false;
                        that.createUndoCommand();
                    }

                    return true;
                }

                return false;
            },

            keyup: function (e) {
                var that = this;

                if (that.systemCommandIsInProgress && that.changed()) {
                    that.systemCommandIsInProgress = false;
                    that.createUndoCommand();
                    return true;
                }

                return false;
            }
        });

        var Keyboard = Class.extend({
            init: function (handlers) {
                this.handlers = handlers;
                this.typingInProgress = false;
            },

            isCharacter: function (keyCode) {
                return (keyCode >= 48 && keyCode <= 90) || (keyCode >= 96 && keyCode <= 111) ||
               (keyCode >= 186 && keyCode <= 192) || (keyCode >= 219 && keyCode <= 222) ||
               keyCode == 229;
            },

            toolFromShortcut: function (tools, e) {
                var key = String.fromCharCode(e.keyCode),
            toolName,
            toolOptions;

                for (toolName in tools) {
                    toolOptions = $.extend({ ctrl: false, alt: false, shift: false }, tools[toolName].options);

                    if ((toolOptions.key == key || toolOptions.key == e.keyCode) &&
                toolOptions.ctrl == e.ctrlKey &&
                toolOptions.alt == e.altKey &&
                toolOptions.shift == e.shiftKey) {
                        return toolName;
                    }
                }
            },

            isTypingKey: function (e) {
                var keyCode = e.keyCode;
                return (this.isCharacter(keyCode) && !e.ctrlKey && !e.altKey) ||
               keyCode == 32 || keyCode == 13 || keyCode == 8 ||
               (keyCode == 46 && !e.shiftKey && !e.ctrlKey && !e.altKey);
            },

            isModifierKey: function (e) {
                var keyCode = e.keyCode;
                return (keyCode == 17 && !e.shiftKey && !e.altKey) ||
               (keyCode == 16 && !e.ctrlKey && !e.altKey) ||
               (keyCode == 18 && !e.ctrlKey && !e.shiftKey);
            },

            isSystem: function (e) {
                return e.keyCode == 46 && e.ctrlKey && !e.altKey && !e.shiftKey;
            },

            startTyping: function (callback) {
                this.onEndTyping = callback;
                this.typingInProgress = true;
            },

            stopTyping: function () {
                if (this.typingInProgress && this.onEndTyping) {
                    this.onEndTyping();
                }
                this.typingInProgress = false;
            },

            endTyping: function (force) {
                var that = this;
                that.clearTimeout();
                if (force) {
                    that.stopTyping();
                } else {
                    that.timeout = window.setTimeout($.proxy(that.stopTyping, that), 1000);
                }
            },

            isTypingInProgress: function () {
                return this.typingInProgress;
            },

            clearTimeout: function () {
                window.clearTimeout(this.timeout);
            },

            notify: function (e, what) {
                var i, handlers = this.handlers;

                for (i = 0; i < handlers.length; i++) {
                    if (handlers[i][what](e)) {
                        break;
                    }
                }
            },

            keydown: function (e) {
                this.notify(e, 'keydown');
            },

            keyup: function (e) {
                this.notify(e, 'keyup');
            }
        });

        var Clipboard = Class.extend({
            init: function (editor) {
                this.editor = editor;
                this.cleaners = [
            new ScriptCleaner(),
            new MSWordFormatCleaner(),
            new WebkitFormatCleaner()
        ];
            },

            htmlToFragment: function (html) {
                var editor = this.editor,
            doc = editor.document,
            container = dom.create(doc, 'div'),
            fragment = doc.createDocumentFragment();

                container.innerHTML = html;

                while (container.firstChild) {
                    fragment.appendChild(container.firstChild);
                }

                return fragment;
            },

            isBlock: function (html) {
                return (/<(div|p|ul|ol|table|h[1-6])/i).test(html);
            },

            _startModification: function () {
                var range;
                var restorePoint;
                var editor = this.editor;

                if (this._inProgress) {
                    return;
                }

                this._inProgress = true;

                range = editor.getRange();
                restorePoint = new RestorePoint(range);

                dom.persistScrollTop(editor.document);

                return { range: range, restorePoint: restorePoint };
            },

            _endModification: function (modificationInfo) {
                finishUpdate(this.editor, modificationInfo.restorePoint);

                this.editor._selectionChange();

                this._inProgress = false;
            },

            _contentModification: function (before, after) {
                var that = this;
                var editor = that.editor;
                var modificationInfo = that._startModification();

                if (!modificationInfo) {
                    return;
                }

                before.call(that, editor, modificationInfo.range);

                setTimeout(function () {
                    after.call(that, editor, modificationInfo.range);

                    that._endModification(modificationInfo);
                });
            },

            oncut: function () {
                this._contentModification($.noop, $.noop);
            },

            _fileToDataURL: function (clipboardItem, complete) {
                var blob = clipboardItem.getAsFile();
                var reader = new FileReader();

                reader.onload = complete || $.noop;

                reader.readAsDataURL(blob);
            },

            _triggerPaste: function (html, options) {
                var args = { html: html || "" };

                args.html = args.html.replace(/\ufeff/g, "");

                this.editor.trigger("paste", args);

                this.paste(args.html, options || {});

            },

            _handleImagePaste: function (e) {
                if (!('FileReader' in window)) {
                    return;
                }

                var that = this;
                var clipboardData = e.clipboardData || e.originalEvent.clipboardData;
                var items = clipboardData && (clipboardData.items || clipboardData.files);

                if (!items || !items.length) {
                    return;
                }

                if (!/^image\//i.test(items[0].type)) {
                    return;
                }

                var modificationInfo = that._startModification();

                if (!modificationInfo) {
                    return;
                }

                this._fileToDataURL(items[0], function (e) {
                    that._triggerPaste('<img src="' + e.target.result + '" />');

                    that._endModification(modificationInfo);
                });

                return true;
            },

            onpaste: function (e) {
                if (this._handleImagePaste(e)) {
                    return;
                }

                this._contentModification(
            function beforePaste(editor, range) {
                var clipboardNode = dom.create(editor.document, 'div', {
                    className: 'k-paste-container',
                    innerHTML: "\ufeff"
                });
                var browser = kendo.support.browser;

                editor.body.appendChild(clipboardNode);

                // text ranges are slow in IE10-, DOM ranges are buggy in IE9-10
                if (browser.msie && browser.version < 11) {
                    e.preventDefault();
                    var r = editor.createRange();
                    r.selectNodeContents(clipboardNode);
                    editor.selectRange(r);
                    var textRange = editor.document.body.createTextRange();
                    textRange.moveToElementText(clipboardNode);
                    $(editor.body).unbind('paste');
                    textRange.execCommand('Paste');
                    $(editor.body).bind('paste', $.proxy(this.onpaste, this));
                } else {
                    var clipboardRange = editor.createRange();
                    clipboardRange.selectNodeContents(clipboardNode);
                    editor.selectRange(clipboardRange);
                }

                range.deleteContents();
            },
            function afterPaste(editor, range) {
                var html = "", args = { html: "" }, containers;
                var browser = kendo.support.browser;

                editor.selectRange(range);

                containers = $(editor.body).children(".k-paste-container");

                containers.each(function () {
                    var lastChild = this.lastChild;

                    if (lastChild && dom.is(lastChild, 'br')) {
                        dom.remove(lastChild);
                    }

                    html += this.innerHTML;
                });

                containers.remove();

                this._triggerPaste(html, { clean: true });
            }
        );
            },

            splittableParent: function (block, node) {
                var parentNode, body;

                if (block) {
                    return dom.closestEditableOfType(node, ['p', 'ul', 'ol']) || node.parentNode;
                }

                parentNode = node.parentNode;
                body = node.ownerDocument.body;

                if (dom.isInline(parentNode)) {
                    while (parentNode.parentNode != body && !dom.isBlock(parentNode.parentNode)) {
                        parentNode = parentNode.parentNode;
                    }
                }

                return parentNode;
            },

            paste: function (html, options) {
                var editor = this.editor,
            i, l;

                options = extend({ clean: false, split: true }, options);

                for (i = 0, l = this.cleaners.length; i < l; i++) {
                    if (this.cleaners[i].applicable(html)) {
                        html = this.cleaners[i].clean(html);
                    }
                }

                if (options.clean) {
                    // remove br elements which immediately precede block elements
                    html = html.replace(/(<br>(\s|&nbsp;)*)+(<\/?(div|p|li|col|t))/ig, "$3");
                    // remove empty inline elements
                    html = html.replace(/<(a|span)[^>]*><\/\1>/ig, "");
                }

                // It is possible in IE to copy just <li> tags
                html = html.replace(/^<li/i, '<ul><li').replace(/li>$/g, 'li></ul>');

                var block = this.isBlock(html);

                editor.focus();
                var range = editor.getRange();
                range.deleteContents();

                if (range.startContainer == editor.document) {
                    range.selectNodeContents(editor.body);
                }

                var marker = new Marker();
                var caret = marker.addCaret(range);

                var parent = this.splittableParent(block, caret);
                var unwrap = false;
                var splittable = parent != editor.body && !dom.is(parent, "td");

                if (options.split && splittable && (block || dom.isInline(parent))) {
                    range.selectNode(caret);
                    editorNS.RangeUtils.split(range, parent, true);
                    unwrap = true;
                }

                var fragment = this.htmlToFragment(html);

                if (fragment.firstChild && fragment.firstChild.className === "k-paste-container") {
                    var fragmentsHtml = [];
                    for (i = 0, l = fragment.childNodes.length; i < l; i++) {
                        fragmentsHtml.push(fragment.childNodes[i].innerHTML);
                    }

                    fragment = this.htmlToFragment(fragmentsHtml.join('<br />'));
                }

                $(fragment.childNodes)
            .filter("table").addClass("k-table").end()
            .find("table").addClass("k-table");

                range.insertNode(fragment);

                parent = this.splittableParent(block, caret);
                if (unwrap) {
                    while (caret.parentNode != parent) {
                        dom.unwrap(caret.parentNode);
                    }

                    dom.unwrap(caret.parentNode);
                }

                dom.normalize(range.commonAncestorContainer);
                caret.style.display = 'inline';
                dom.restoreScrollTop(editor.document);
                dom.scrollTo(caret);
                marker.removeCaret(range);
                editor.selectRange(range);
            }
        });

        var Cleaner = Class.extend({
            clean: function (html) {
                var that = this,
            replacements = that.replacements,
            i, l;

                for (i = 0, l = replacements.length; i < l; i += 2) {
                    html = html.replace(replacements[i], replacements[i + 1]);
                }

                return html;
            }
        });

        var ScriptCleaner = Cleaner.extend({
            init: function () {
                this.replacements = [
            /<(\/?)script([^>]*)>/i, "<$1telerik:script$2>"
        ];
            },

            applicable: function (html) {
                return (/<script[^>]*>/i).test(html);
            }
        });

        var MSWordFormatCleaner = Cleaner.extend({
            init: function () {
                this.replacements = [
            /<\?xml[^>]*>/gi, '',
            /<!--(.|\n)*?-->/g, '', /* comments */
            /&quot;/g, "'", /* encoded quotes (in attributes) */
            /(?:<br>&nbsp;[\s\r\n]+|<br>)*(<\/?(h[1-6]|hr|p|div|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|address|pre|form|blockquote|dl|dt|dd|dir|fieldset)[^>]*>)(?:<br>&nbsp;[\s\r\n]+|<br>)*/g, '$1',
            /<br><br>/g, '<BR><BR>',
            /<br>(?!\n)/g, ' ',
            /<table([^>]*)>(\s|&nbsp;)+<t/gi, '<table$1><t',
            /<tr[^>]*>(\s|&nbsp;)*<\/tr>/gi, '',
            /<tbody[^>]*>(\s|&nbsp;)*<\/tbody>/gi, '',
            /<table[^>]*>(\s|&nbsp;)*<\/table>/gi, '',
            /<BR><BR>/g, '<br>',
            /^\s*(&nbsp;)+/gi, '',
            /(&nbsp;|<br[^>]*>)+\s*$/gi, '',
            /mso-[^;"]*;?/ig, '', /* office-related CSS attributes */
            /<(\/?)b(\s[^>]*)?>/ig, '<$1strong$2>',
            /<(\/?)i(\s[^>]*)?>/ig, '<$1em$2>',
            /<o:p>&nbsp;<\/o:p>/ig, '&nbsp;',
            /<\/?(meta|link|style|o:|v:|x:)[^>]*>((?:.|\n)*?<\/(meta|link|style|o:|v:|x:)[^>]*>)?/ig, '', /* external references and namespaced tags */
            /<\/o>/g, '',
            /style=(["|'])\s*\1/g, '', /* empty style attributes */
            /(<br[^>]*>)?\n/g, function ($0, $1) { return $1 ? $0 : ' '; } /* phantom extra line feeds */
        ];
            },

            applicable: function (html) {
                return (/class="?Mso|style="[^"]*mso-/i).test(html) || (/urn:schemas-microsoft-com:office/).test(html);
            },

            stripEmptyAnchors: function (html) {
                return html.replace(/<a([^>]*)>\s*<\/a>/ig, function (a, attributes) {
                    if (!attributes || attributes.indexOf("href") < 0) {
                        return "";
                    }

                    return a;
                });
            },

            listType: function (html) {
                var startingSymbol;

                if (/^(<span [^>]*texhtml[^>]*>)?<span [^>]*(Symbol|Wingdings)[^>]*>/i.test(html)) {
                    startingSymbol = true;
                }

                html = html.replace(/<\/?\w+[^>]*>/g, '').replace(/&nbsp;/g, '\u00a0');

                if ((!startingSymbol && /^[\u2022\u00b7\u00a7\u00d8o]\u00a0+/.test(html)) ||
            (startingSymbol && /^.\u00a0+/.test(html))) {
                    return 'ul';
                }

                if (/^\s*\w+[\.\)]\u00a0{2,}/.test(html)) {
                    return 'ol';
                }
            },

            _convertToLi: function (p) {
                var content;

                if (p.childNodes.length == 1) {
                    content = p.firstChild.innerHTML.replace(/^\w+[\.\)](&nbsp;)+ /, "");
                } else {
                    dom.remove(p.firstChild);

                    // check for roman numerals
                    if (p.firstChild.nodeType == 3) {
                        if (/^[ivx]+\.$/i.test(p.firstChild.nodeValue)) {
                            dom.remove(p.firstChild);
                        }
                    }

                    if (/^(&nbsp;|\s)+$/i.test(p.firstChild.innerHTML)) {
                        dom.remove(p.firstChild);
                    }

                    content = p.innerHTML;
                }

                dom.remove(p);

                return dom.create(document, 'li', { innerHTML: content });
            },

            lists: function (placeholder) {
                var blockChildren = $(dom.blockElements.join(','), placeholder),
            lastMargin = -1,
            lastType,
            name,
            levels = { 'ul': {}, 'ol': {} },
            li = placeholder,
            i, p, type, margin, list, key, child;

                for (i = 0; i < blockChildren.length; i++) {
                    p = blockChildren[i];
                    type = this.listType(p.innerHTML);
                    name = dom.name(p);

                    if (name == "td") {
                        continue;
                    }

                    if (!type || name != 'p') {
                        if (!p.innerHTML) {
                            dom.remove(p);
                        } else {
                            levels = { 'ul': {}, 'ol': {} };
                            li = placeholder;
                            lastMargin = -1;
                        }
                        continue;
                    }

                    margin = parseFloat(p.style.marginLeft || 0);
                    list = levels[type][margin];

                    if (margin > lastMargin || !list) {
                        list = dom.create(document, type);

                        if (li == placeholder) {
                            dom.insertBefore(list, p);
                        } else {
                            li.appendChild(list);
                        }

                        levels[type][margin] = list;
                    }

                    if (lastType != type) {
                        for (key in levels) {
                            for (child in levels[key]) {
                                if ($.contains(list, levels[key][child])) {
                                    delete levels[key][child];
                                }
                            }
                        }
                    }

                    li = this._convertToLi(p);

                    list.appendChild(li);
                    lastMargin = margin;
                    lastType = type;
                }
            },

            removeAttributes: function (element) {
                var attributes = element.attributes,
            i = attributes.length;

                while (i--) {
                    if (dom.name(attributes[i]) != "colspan") {
                        element.removeAttributeNode(attributes[i]);
                    }
                }
            },

            createColGroup: function (row) {
                var cells = row.cells;
                var table = $(row).closest("table");
                var colgroup = table.children("colgroup");

                if (cells.length < 2) {
                    return;
                } else if (colgroup.length) {
                    cells = colgroup.children();
                    colgroup[0].parentNode.removeChild(colgroup[0]);
                }

                colgroup = $($.map(cells, function (cell) {
                    var width = cell.width;
                    if (width && parseInt(width, 10) !== 0) {
                        return kendo.format('<col style="width:{0}px;"/>', width);
                    }

                    return "<col />";
                }).join(""));

                // jquery 1.9/2.0 discrepancy
                if (!colgroup.is("colgroup")) {
                    colgroup = $("<colgroup/>").append(colgroup);
                }

                colgroup.prependTo(table);
            },

            convertHeaders: function (row) {
                var cells = row.cells,
            i,
            boldedCells = $.map(cells, function (cell) {
                var child = $(cell).children("p").children("strong")[0];

                if (child && dom.name(child) == "strong") {
                    return child;
                }
            });

                if (boldedCells.length == cells.length) {
                    for (i = 0; i < boldedCells.length; i++) {
                        dom.unwrap(boldedCells[i]);
                    }

                    $(row).closest("table")
                .find("colgroup").after("<thead></thead>").end()
                .find("thead").append(row);

                    for (i = 0; i < cells.length; i++) {
                        dom.changeTag(cells[i], "th");
                    }
                }
            },

            removeParagraphs: function (cells) {
                var i, j, len, cell, paragraphs;

                for (i = 0; i < cells.length; i++) {
                    this.removeAttributes(cells[i]);

                    // remove paragraphs and insert line breaks between them
                    cell = $(cells[i]);
                    paragraphs = cell.children("p");

                    for (j = 0, len = paragraphs.length; j < len; j++) {
                        if (j < len - 1) {
                            dom.insertAfter(dom.create(document, "br"), paragraphs[j]);
                        }

                        dom.unwrap(paragraphs[j]);
                    }
                }
            },

            removeDefaultColors: function (spans) {
                for (var i = 0; i < spans.length; i++) {
                    if (/^\s*color:\s*[^;]*;?$/i.test(spans[i].style.cssText)) {
                        dom.unwrap(spans[i]);
                    }
                }
            },

            tables: function (placeholder) {
                var tables = $(placeholder).find("table"),
            that = this,
            rows,
            firstRow, longestRow, i, j;

                for (i = 0; i < tables.length; i++) {
                    rows = tables[i].rows;
                    longestRow = firstRow = rows[0];

                    for (j = 1; j < rows.length; j++) {
                        if (rows[j].cells.length > longestRow.cells.length) {
                            longestRow = rows[j];
                        }
                    }

                    that.createColGroup(longestRow);
                    that.convertHeaders(firstRow);

                    that.removeAttributes(tables[i]);

                    that.removeParagraphs(tables.eq(i).find("td,th"));
                    that.removeDefaultColors(tables.eq(i).find("span"));
                }
            },

            headers: function (placeholder) {
                var titles = $(placeholder).find("p.MsoTitle");

                for (var i = 0; i < titles.length; i++) {
                    dom.changeTag(titles[i], "h1");
                }
            },

            clean: function (html) {
                var that = this, placeholder;

                html = Cleaner.fn.clean.call(that, html);
                html = that.stripEmptyAnchors(html);

                placeholder = dom.create(document, 'div', { innerHTML: html });
                that.headers(placeholder);
                that.lists(placeholder);
                that.tables(placeholder);

                html = placeholder.innerHTML.replace(/(<[^>]*)\s+class="?[^"\s>]*"?/ig, '$1');

                return html;
            }
        });

        var WebkitFormatCleaner = Cleaner.extend({
            init: function () {
                this.replacements = [
            /\s+class="Apple-style-span[^"]*"/gi, '',
            /<(div|p|h[1-6])\s+style="[^"]*"/gi, '<$1',
            /^<div>(.*)<\/div>$/, '$1'
        ];
            },

            applicable: function (html) {
                return (/class="?Apple-style-span|style="[^"]*-webkit-nbsp-mode/i).test(html);
            }
        });

        extend(editorNS, {
            Command: Command,
            GenericCommand: GenericCommand,
            InsertHtmlCommand: InsertHtmlCommand,
            InsertHtmlTool: InsertHtmlTool,
            UndoRedoStack: UndoRedoStack,
            TypingHandler: TypingHandler,
            SystemHandler: SystemHandler,
            BackspaceHandler: BackspaceHandler,
            Keyboard: Keyboard,
            Clipboard: Clipboard,
            Cleaner: Cleaner,
            MSWordFormatCleaner: MSWordFormatCleaner,
            WebkitFormatCleaner: WebkitFormatCleaner
        });

        registerTool("insertHtml", new InsertHtmlTool({ template: new ToolTemplate({ template: EditorUtils.dropDownListTemplate, title: "Insert HTML", initialValue: "Insert HTML" }) }));

    })(window.kendo.jQuery);

    (function ($) {

        var kendo = window.kendo,
    Class = kendo.Class,
    Editor = kendo.ui.editor,
    formats = kendo.ui.Editor.fn.options.formats,
    EditorUtils = Editor.EditorUtils,
    Tool = Editor.Tool,
    ToolTemplate = Editor.ToolTemplate,
    FormatTool = Editor.FormatTool,
    dom = Editor.Dom,
    RangeUtils = Editor.RangeUtils,
    extend = $.extend,
    registerTool = Editor.EditorUtils.registerTool,
    registerFormat = Editor.EditorUtils.registerFormat,
    KMARKER = "k-marker";

        var InlineFormatFinder = Class.extend({
            init: function (format) {
                this.format = format;
            },

            numberOfSiblings: function (referenceNode) {
                var textNodesCount = 0,
            elementNodesCount = 0,
            markerCount = 0,
            parentNode = referenceNode.parentNode,
            node;

                for (node = parentNode.firstChild; node; node = node.nextSibling) {
                    if (node != referenceNode) {
                        if (node.className == KMARKER) {
                            markerCount++;
                        } else if (node.nodeType == 3) {
                            textNodesCount++;
                        } else {
                            elementNodesCount++;
                        }
                    }
                }

                if (markerCount > 1 && parentNode.firstChild.className == KMARKER && parentNode.lastChild.className == KMARKER) {
                    // full node selection
                    return 0;
                } else {
                    return elementNodesCount + textNodesCount;
                }
            },

            findSuitable: function (sourceNode, skip) {
                if (!skip && this.numberOfSiblings(sourceNode) > 0) {
                    return null;
                }

                return dom.parentOfType(sourceNode, this.format[0].tags);
            },

            findFormat: function (sourceNode) {
                var format = this.format,
            attrEquals = dom.attrEquals,
            i, len, node, tags, attributes;

                for (i = 0, len = format.length; i < len; i++) {
                    node = sourceNode;
                    tags = format[i].tags;
                    attributes = format[i].attr;

                    if (node && dom.ofType(node, tags) && attrEquals(node, attributes)) {
                        return node;
                    }

                    while (node) {
                        node = dom.parentOfType(node, tags);
                        if (node && attrEquals(node, attributes)) {
                            return node;
                        }
                    }
                }

                return null;
            },

            isFormatted: function (nodes) {
                var i, len;

                for (i = 0, len = nodes.length; i < len; i++) {
                    if (this.findFormat(nodes[i])) {
                        return true;
                    }
                }
                return false;
            }
        });

        var InlineFormatter = Class.extend({
            init: function (format, values) {
                this.finder = new InlineFormatFinder(format);
                this.attributes = extend({}, format[0].attr, values);
                this.tag = format[0].tags[0];
            },

            wrap: function (node) {
                return dom.wrap(node, dom.create(node.ownerDocument, this.tag, this.attributes));
            },

            activate: function (range, nodes) {
                if (this.finder.isFormatted(nodes)) {
                    this.split(range);
                    this.remove(nodes);
                } else {
                    this.apply(nodes);
                }
            },

            toggle: function (range) {
                var nodes = RangeUtils.textNodes(range);

                if (nodes.length > 0) {
                    this.activate(range, nodes);
                }
            },

            apply: function (nodes) {
                var formatNodes = [];
                var i, l, node, formatNode;

                for (i = 0, l = nodes.length; i < l; i++) {
                    node = nodes[i];
                    formatNode = this.finder.findSuitable(node);
                    if (formatNode) {
                        dom.attr(formatNode, this.attributes);
                    } else {
                        formatNode = this.wrap(node);
                    }

                    formatNodes.push(formatNode);
                }

                this.consolidate(formatNodes);
            },

            remove: function (nodes) {
                var i, l, formatNode;

                for (i = 0, l = nodes.length; i < l; i++) {
                    formatNode = this.finder.findFormat(nodes[i]);
                    if (formatNode) {
                        if (this.attributes && this.attributes.style) {
                            dom.unstyle(formatNode, this.attributes.style);
                            if (!formatNode.style.cssText && !formatNode.attributes["class"]) {
                                dom.unwrap(formatNode);
                            }
                        } else {
                            dom.unwrap(formatNode);
                        }
                    }
                }
            },

            split: function (range) {
                var nodes = RangeUtils.textNodes(range);
                var l = nodes.length;
                var i, formatNode;

                if (l > 0) {
                    for (i = 0; i < l; i++) {
                        formatNode = this.finder.findFormat(nodes[i]);
                        if (formatNode) {
                            RangeUtils.split(range, formatNode, true);
                        }
                    }
                }
            },

            consolidate: function (nodes) {
                var node, last;

                while (nodes.length > 1) {
                    node = nodes.pop();
                    last = nodes[nodes.length - 1];

                    if (node.previousSibling && node.previousSibling.className == KMARKER) {
                        last.appendChild(node.previousSibling);
                    }

                    if (node.tagName == last.tagName && node.previousSibling == last && node.style.cssText == last.style.cssText) {
                        while (node.firstChild) {
                            last.appendChild(node.firstChild);
                        }
                        dom.remove(node);
                    }
                }
            }
        });

        var GreedyInlineFormatFinder = InlineFormatFinder.extend({
            init: function (format, greedyProperty) {
                this.format = format;
                this.greedyProperty = greedyProperty;
                InlineFormatFinder.fn.init.call(this, format);
            },

            getInlineCssValue: function (node) {
                var attributes = node.attributes;
                var trim = $.trim;
                var i, l, attribute, name, attributeValue, css, pair, cssIndex, len;
                var propertyAndValue, property, value;

                if (!attributes) {
                    return;
                }

                for (i = 0, l = attributes.length; i < l; i++) {
                    attribute = attributes[i];
                    name = attribute.nodeName;
                    attributeValue = attribute.nodeValue;

                    if (attribute.specified && name == "style") {

                        css = trim(attributeValue || node.style.cssText).split(";");

                        for (cssIndex = 0, len = css.length; cssIndex < len; cssIndex++) {
                            pair = css[cssIndex];
                            if (pair.length) {
                                propertyAndValue = pair.split(":");
                                property = trim(propertyAndValue[0].toLowerCase());
                                value = trim(propertyAndValue[1]);

                                if (property != this.greedyProperty) {
                                    continue;
                                }

                                return property.indexOf("color") >= 0 ? dom.toHex(value) : value;
                            }
                        }
                    }
                }
            },

            getFormatInner: function (node) {
                var $node = $(dom.isDataNode(node) ? node.parentNode : node);
                var parents = $node.parentsUntil("[contentEditable]").addBack().toArray().reverse();
                var i, len, value;

                for (i = 0, len = parents.length; i < len; i++) {
                    value = this.greedyProperty == "className" ? parents[i].className : this.getInlineCssValue(parents[i]);
                    if (value) {
                        return value;
                    }
                }

                return "inherit";
            },

            getFormat: function (nodes) {
                var result = this.getFormatInner(nodes[0]), i, len;

                for (i = 1, len = nodes.length; i < len; i++) {
                    if (result != this.getFormatInner(nodes[i])) {
                        return "";
                    }
                }

                return result;
            },

            isFormatted: function (nodes) {
                return this.getFormat(nodes) !== "";
            }
        });

        var GreedyInlineFormatter = InlineFormatter.extend({
            init: function (format, values, greedyProperty) {
                InlineFormatter.fn.init.call(this, format, values);

                this.values = values;
                this.finder = new GreedyInlineFormatFinder(format, greedyProperty);

                if (greedyProperty) {
                    this.greedyProperty = kendo.toCamelCase(greedyProperty);
                }

            },

            activate: function (range, nodes) {
                var greedyProperty = this.greedyProperty;
                var action = "apply";

                this.split(range);

                if (greedyProperty && this.values.style[greedyProperty] == "inherit") {
                    action = "remove";
                }

                this[action](nodes);
            }
        });

        var InlineFormatTool = FormatTool.extend({
            init: function (options) {
                FormatTool.fn.init.call(this, extend(options, {
                    finder: new InlineFormatFinder(options.format),
                    formatter: function () { return new InlineFormatter(options.format); }
                }));
            }
        });

        var DelayedExecutionTool = Tool.extend({
            update: function (ui, nodes) {
                var list = ui.data(this.type);

                list.close();
                list.value(this.finder.getFormat(nodes));
            }
        });

        var FontTool = DelayedExecutionTool.extend({
            init: function (options) {
                Tool.fn.init.call(this, options);

                // IE has single selection hence we are using select box instead of combobox
                this.type = (kendo.support.browser.msie || kendo.support.touch) ? "kendoDropDownList" : "kendoComboBox";
                this.format = [{ tags: ["span"]}];
                this.finder = new GreedyInlineFormatFinder(this.format, options.cssAttr);
            },

            command: function (commandArguments) {
                var options = this.options,
            format = this.format,
            style = {};

                return new Editor.FormatCommand(extend(commandArguments, {
                    formatter: function () {
                        style[options.domAttr] = commandArguments.value;

                        return new GreedyInlineFormatter(format, { style: style }, options.cssAttr);
                    }
                }));
            },

            initialize: function (ui, initOptions) {
                var editor = initOptions.editor,
            options = this.options,
            toolName = options.name,
            dataSource,
            defaultValue = [];

                if (options.defaultValue) {
                    defaultValue = [{
                        text: editor.options.messages[options.defaultValue[0].text],
                        value: options.defaultValue[0].value
                    }];
                }

                dataSource = defaultValue.concat(options.items ? options.items : (editor.options[toolName] || []));

                ui[this.type]({
                    dataTextField: "text",
                    dataValueField: "value",
                    dataSource: dataSource,
                    change: function () {
                        Tool.exec(editor, toolName, this.value());
                    },
                    highlightFirst: false
                });

                ui.closest(".k-widget").removeClass("k-" + toolName).find("*").addBack().attr("unselectable", "on");

                ui.data(this.type).value("inherit");
            }

        });

        var ColorTool = Tool.extend({
            init: function (options) {
                Tool.fn.init.call(this, options);

                this.format = [{ tags: ["span"]}];
                this.finder = new GreedyInlineFormatFinder(this.format, options.cssAttr);
            },

            options: {
                palette: "websafe"
            },

            update: function () {
                this._widget.close();
            },

            command: function (commandArguments) {
                var options = this.options,
            format = this.format,
            style = {};

                return new Editor.FormatCommand(extend(commandArguments, {
                    formatter: function () {
                        style[options.domAttr] = commandArguments.value;

                        return new GreedyInlineFormatter(format, { style: style }, options.cssAttr);
                    }
                }));
            },

            initialize: function (ui, initOptions) {
                var editor = initOptions.editor,
            toolName = this.name,
            options = extend({}, ColorTool.fn.options, this.options),
            palette = options.palette;

                ui = this._widget = new kendo.ui.ColorPicker(ui, {
                    value: $.isArray(palette) ? palette[0] : "#000",
                    toolIcon: "k-" + options.name,
                    palette: palette,
                    change: function () {
                        var color = ui.value();
                        if (color) {
                            Tool.exec(editor, toolName, color);
                        }
                        editor.focus();
                    },
                    activate: function (e) {
                        e.preventDefault();
                        ui.trigger("change");
                    }
                });
                ui.wrapper
            .attr({ title: initOptions.title, unselectable: "on" })
            .find("*").attr("unselectable", "on");
            }
        });

        extend(Editor, {
            InlineFormatFinder: InlineFormatFinder,
            InlineFormatter: InlineFormatter,
            DelayedExecutionTool: DelayedExecutionTool,
            GreedyInlineFormatFinder: GreedyInlineFormatFinder,
            GreedyInlineFormatter: GreedyInlineFormatter,
            InlineFormatTool: InlineFormatTool,
            FontTool: FontTool,
            ColorTool: ColorTool
        });

        registerFormat("bold", [{ tags: ["strong", "b"] }, { tags: ["span"], attr: { style: { fontWeight: "bold"}}}]);
        registerTool("bold", new InlineFormatTool({ key: "B", ctrl: true, format: formats.bold, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Bold" }) }));

        registerFormat("italic", [{ tags: ["em", "i"] }, { tags: ["span"], attr: { style: { fontStyle: "italic"}}}]);
        registerTool("italic", new InlineFormatTool({ key: "I", ctrl: true, format: formats.italic, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Italic" }) }));

        registerFormat("underline", [{ tags: ["span"], attr: { style: { textDecoration: "underline"}} }, { tags: ["u"]}]);
        registerTool("underline", new InlineFormatTool({ key: "U", ctrl: true, format: formats.underline, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Underline" }) }));

        registerFormat("strikethrough", [{ tags: ["del", "strike"] }, { tags: ["span"], attr: { style: { textDecoration: "line-through"}}}]);
        registerTool("strikethrough", new InlineFormatTool({ format: formats.strikethrough, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Strikethrough" }) }));

        registerFormat("superscript", [{ tags: ["sup"]}]);
        registerTool("superscript", new InlineFormatTool({ format: formats.superscript, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Superscript" }) }));

        registerFormat("subscript", [{ tags: ["sub"]}]);
        registerTool("subscript", new InlineFormatTool({ format: formats.subscript, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Subscript" }) }));

        registerTool("foreColor", new ColorTool({ cssAttr: "color", domAttr: "color", name: "foreColor", template: new ToolTemplate({ template: EditorUtils.colorPickerTemplate, title: "Color" }) }));

        registerTool("backColor", new ColorTool({ cssAttr: "background-color", domAttr: "backgroundColor", name: "backColor", template: new ToolTemplate({ template: EditorUtils.colorPickerTemplate, title: "Background Color" }) }));

        registerTool("fontName", new FontTool({ cssAttr: "font-family", domAttr: "fontFamily", name: "fontName", defaultValue: [{ text: "fontNameInherit", value: "inherit"}], template: new ToolTemplate({ template: EditorUtils.comboBoxTemplate, title: "Font Name" }) }));

        registerTool("fontSize", new FontTool({ cssAttr: "font-size", domAttr: "fontSize", name: "fontSize", defaultValue: [{ text: "fontSizeInherit", value: "inherit"}], template: new ToolTemplate({ template: EditorUtils.comboBoxTemplate, title: "Font Size" }) }));

    })(window.kendo.jQuery);

    (function ($) {

        var kendo = window.kendo,
    Class = kendo.Class,
    extend = $.extend,
    Editor = kendo.ui.editor,
    formats = kendo.ui.Editor.fn.options.formats,
    dom = Editor.Dom,
    Command = Editor.Command,
    ToolTemplate = Editor.ToolTemplate,
    FormatTool = Editor.FormatTool,
    EditorUtils = Editor.EditorUtils,
    registerTool = EditorUtils.registerTool,
    registerFormat = EditorUtils.registerFormat,
    RangeUtils = Editor.RangeUtils;

        var BlockFormatFinder = Class.extend({
            init: function (format) {
                this.format = format;
            },

            contains: function (node, children) {
                var i, len, child;

                for (i = 0, len = children.length; i < len; i++) {
                    child = children[i];
                    if (!child || !dom.isAncestorOrSelf(node, child)) {
                        return false;
                    }
                }

                return true;
            },

            findSuitable: function (nodes) {
                var format = this.format,
            suitable = [],
            i, len, candidate;

                for (i = 0, len = nodes.length; i < len; i++) {
                    for (var f = format.length - 1; f >= 0; f--) {
                        candidate = dom.ofType(nodes[i], format[f].tags) ? nodes[i] : dom.parentOfType(nodes[i], format[f].tags);
                        if (candidate) {
                            break;
                        }
                    }

                    if (!candidate || candidate.contentEditable === 'true') {
                        return [];
                    }

                    if ($.inArray(candidate, suitable) < 0) {
                        suitable.push(candidate);
                    }
                }

                for (i = 0, len = suitable.length; i < len; i++) {
                    if (this.contains(suitable[i], suitable)) {
                        return [suitable[i]];
                    }
                }

                return suitable;
            },

            findFormat: function (sourceNode) {
                var format = this.format,
            i, len, node, tags, attributes;
                var editableParent = dom.editableParent(sourceNode);

                for (i = 0, len = format.length; i < len; i++) {
                    node = sourceNode;
                    tags = format[i].tags;
                    attributes = format[i].attr;

                    while (node && dom.isAncestorOf(editableParent, node)) {
                        if (dom.ofType(node, tags) && dom.attrEquals(node, attributes)) {
                            return node;
                        }

                        node = node.parentNode;
                    }
                }
                return null;
            },

            getFormat: function (nodes) {
                var that = this,
            findFormat = function (node) {
                return that.findFormat(dom.isDataNode(node) ? node.parentNode : node);
            },
            result = findFormat(nodes[0]),
            i, len;

                if (!result) {
                    return "";
                }

                for (i = 1, len = nodes.length; i < len; i++) {
                    if (result != findFormat(nodes[i])) {
                        return "";
                    }
                }

                return result.nodeName.toLowerCase();
            },

            isFormatted: function (nodes) {
                for (var i = 0, len = nodes.length; i < len; i++) {
                    if (!this.findFormat(nodes[i])) {
                        return false;
                    }
                }

                return true;
            }
        });

        var BlockFormatter = Class.extend({
            init: function (format, values) {
                this.format = format;
                this.values = values;
                this.finder = new BlockFormatFinder(format);
            },

            wrap: function (tag, attributes, nodes) {
                var commonAncestor = nodes.length == 1 ? dom.blockParentOrBody(nodes[0]) : dom.commonAncestor.apply(null, nodes);

                if (dom.isInline(commonAncestor)) {
                    commonAncestor = dom.blockParentOrBody(commonAncestor);
                }

                var ancestors = dom.significantChildNodes(commonAncestor),
            position = dom.findNodeIndex(ancestors[0]),
            wrapper = dom.create(commonAncestor.ownerDocument, tag, attributes),
            i, ancestor;

                for (i = 0; i < ancestors.length; i++) {
                    ancestor = ancestors[i];
                    if (dom.isBlock(ancestor)) {
                        dom.attr(ancestor, attributes);

                        if (wrapper.childNodes.length) {
                            dom.insertBefore(wrapper, ancestor);
                            wrapper = wrapper.cloneNode(false);
                        }

                        position = dom.findNodeIndex(ancestor) + 1;

                        continue;
                    }

                    wrapper.appendChild(ancestor);
                }

                if (wrapper.firstChild) {
                    dom.insertAt(commonAncestor, wrapper, position);
                }
            },

            apply: function (nodes) {
                var format, values = this.values;

                function attributes(format) {
                    return extend({}, format && format.attr, values);
                }

                var images = dom.filter("img", nodes);
                var imageFormat = EditorUtils.formatByName("img", this.format);
                var imageAttributes = attributes(imageFormat);
                $.each(images, function () {
                    dom.attr(this, imageAttributes);
                });

                // only images were selected, no need to wrap
                if (images.length == nodes.length) {
                    return;
                }

                var nonImages = dom.filter("img", nodes, true);
                var formatNodes = this.finder.findSuitable(nonImages);

                if (formatNodes.length) {
                    for (var i = 0, len = formatNodes.length; i < len; i++) {
                        format = EditorUtils.formatByName(dom.name(formatNodes[i]), this.format);
                        dom.attr(formatNodes[i], attributes(format));
                    }
                } else {
                    format = this.format[0];
                    this.wrap(format.tags[0], attributes(format), nonImages);
                }
            },

            remove: function (nodes) {
                var i, l, formatNode, namedFormat, name;

                for (i = 0, l = nodes.length; i < l; i++) {
                    formatNode = this.finder.findFormat(nodes[i]);
                    if (formatNode) {
                        name = dom.name(formatNode);
                        if (name == "div" && !formatNode.getAttribute("class")) {
                            dom.unwrap(formatNode);
                        } else {
                            namedFormat = EditorUtils.formatByName(name, this.format);
                            if (namedFormat.attr.style) {
                                dom.unstyle(formatNode, namedFormat.attr.style);
                            }
                            if (namedFormat.attr.className) {
                                dom.removeClass(formatNode, namedFormat.attr.className);
                            }
                        }
                    }
                }
            },

            toggle: function (range) {
                var that = this,
            nodes = RangeUtils.nodes(range);

                if (that.finder.isFormatted(nodes)) {
                    that.remove(nodes);
                } else {
                    that.apply(nodes);
                }
            }
        });

        var GreedyBlockFormatter = Class.extend({
            init: function (format, values) {
                var that = this;
                that.format = format;
                that.values = values;
                that.finder = new BlockFormatFinder(format);
            },

            apply: function (nodes) {
                var format = this.format;
                var blocks = dom.blockParents(nodes);
                var formatTag = format[0].tags[0];
                var i, len, list, formatter, range;
                var element;
                var tagName;

                if (blocks.length) {
                    for (i = 0, len = blocks.length; i < len; i++) {
                        tagName = dom.name(blocks[i]);

                        if (tagName == "li") {
                            list = blocks[i].parentNode;
                            formatter = new Editor.ListFormatter(list.nodeName.toLowerCase(), formatTag);
                            range = this.editor.createRange();
                            range.selectNode(blocks[i]);
                            formatter.toggle(range);
                        } else if (formatTag && (tagName == "td" || blocks[i].attributes.contentEditable)) {
                            new BlockFormatter(format, this.values).apply(blocks[i].childNodes);
                        } else {
                            element = dom.changeTag(blocks[i], formatTag);
                            dom.attr(element, format[0].attr);
                        }
                    }
                } else {
                    new BlockFormatter(format, this.values).apply(nodes);
                }
            },

            toggle: function (range) {
                var nodes = RangeUtils.textNodes(range);
                if (!nodes.length) {
                    range.selectNodeContents(range.commonAncestorContainer);
                    nodes = RangeUtils.textNodes(range);
                    if (!nodes.length) {
                        nodes = dom.significantChildNodes(range.commonAncestorContainer);
                    }
                }

                this.apply(nodes);
            }
        });

        var FormatCommand = Command.extend({
            init: function (options) {
                options.formatter = options.formatter();
                Command.fn.init.call(this, options);
            }
        });

        var BlockFormatTool = FormatTool.extend({ init: function (options) {
            FormatTool.fn.init.call(this, extend(options, {
                finder: new BlockFormatFinder(options.format),
                formatter: function () {
                    return new BlockFormatter(options.format);
                }
            }));
        }
        });

        extend(Editor, {
            BlockFormatFinder: BlockFormatFinder,
            BlockFormatter: BlockFormatter,
            GreedyBlockFormatter: GreedyBlockFormatter,
            FormatCommand: FormatCommand,
            BlockFormatTool: BlockFormatTool
        });

        var listElements = ["ul", "ol", "li"];

        registerFormat("justifyLeft", [
    { tags: dom.nonListBlockElements, attr: { style: { textAlign: "left"}} },
    { tags: ["img"], attr: { style: { "float": "left", display: "", marginLeft: "", marginRight: ""}} },
    { tags: listElements, attr: { style: { textAlign: "left", listStylePosition: ""}} }
]);
        registerTool("justifyLeft", new BlockFormatTool({
            format: formats.justifyLeft,
            template: new ToolTemplate({
                template: EditorUtils.buttonTemplate,
                title: "Justify Left"
            })
        }));

        registerFormat("justifyCenter", [
    { tags: dom.nonListBlockElements, attr: { style: { textAlign: "center"}} },
    { tags: ["img"], attr: { style: { display: "block", marginLeft: "auto", marginRight: "auto", "float": ""}} },
    { tags: listElements, attr: { style: { textAlign: "center", listStylePosition: "inside"}} }
]);
        registerTool("justifyCenter", new BlockFormatTool({
            format: formats.justifyCenter,
            template: new ToolTemplate({
                template: EditorUtils.buttonTemplate,
                title: "Justify Center"
            })
        }));

        registerFormat("justifyRight", [
    { tags: dom.nonListBlockElements, attr: { style: { textAlign: "right"}} },
    { tags: ["img"], attr: { style: { "float": "right", display: "", marginLeft: "", marginRight: ""}} },
    { tags: listElements, attr: { style: { textAlign: "right", listStylePosition: "inside"}} }
]);
        registerTool("justifyRight", new BlockFormatTool({
            format: formats.justifyRight,
            template: new ToolTemplate({
                template: EditorUtils.buttonTemplate,
                title: "Justify Right"
            })
        }));

        registerFormat("justifyFull", [
    { tags: dom.nonListBlockElements, attr: { style: { textAlign: "justify"}} },
    { tags: ["img"], attr: { style: { display: "block", marginLeft: "auto", marginRight: "auto", "float": ""}} },
    { tags: listElements, attr: { style: { textAlign: "justify", listStylePosition: ""}} }
]);
        registerTool("justifyFull", new BlockFormatTool({
            format: formats.justifyFull,
            template: new ToolTemplate({
                template: EditorUtils.buttonTemplate,
                title: "Justify Full"
            })
        }));

    })(window.kendo.jQuery);

    (function ($) {

        // Imports ================================================================
        var kendo = window.kendo,
    extend = $.extend,
    editorNS = kendo.ui.editor,
    dom = editorNS.Dom,
    Command = editorNS.Command,
    Tool = editorNS.Tool,
    BlockFormatter = editorNS.BlockFormatter,
    normalize = dom.normalize,
    RangeUtils = editorNS.RangeUtils,
    registerTool = editorNS.EditorUtils.registerTool;

        var ParagraphCommand = Command.extend({
            init: function (options) {
                this.options = options;
                Command.fn.init.call(this, options);
            },

            _insertMarker: function (doc, range) {
                var marker = dom.create(doc, 'a'), container;
                marker.className = "k-marker";

                range.insertNode(marker);

                if (!marker.parentNode) {
                    // inserting paragraph in Firefox full body range
                    container = range.commonAncestorContainer;
                    container.innerHTML = "";
                    container.appendChild(marker);
                }

                normalize(marker.parentNode);

                return marker;
            },

            _moveFocus: function (range, next) {
                if (dom.is(next, 'img')) {
                    range.setStartBefore(next);
                } else {
                    range.selectNodeContents(next);

                    var textNode = RangeUtils.textNodes(range)[0];

                    if (textNode) {
                        if (dom.is(textNode, 'img')) {
                            range.setStartBefore(textNode);
                        } else {
                            range.selectNodeContents(textNode);
                        }
                    } else {
                        while (next.childNodes.length && !dom.is(next.firstChild, "br")) {
                            next = next.firstChild;
                        }

                        range.selectNodeContents(next);
                    }
                }
            },

            shouldTrim: function (range) {
                var blocks = 'p,h1,h2,h3,h4,h5,h6'.split(','),
            startInBlock = dom.parentOfType(range.startContainer, blocks),
            endInBlock = dom.parentOfType(range.endContainer, blocks);
                return (startInBlock && !endInBlock) || (!startInBlock && endInBlock);
            },

            _blankAfter: function (node) {
                while (node && (dom.isMarker(node) || dom.stripBom(node.nodeValue) === "")) {
                    node = node.nextSibling;
                }

                return !node;
            },

            exec: function () {
                var range = this.getRange(),
            doc = RangeUtils.documentFromRange(range),
            parent, previous, next,
            emptyParagraphContent = editorNS.emptyElementContent,
            paragraph, marker, li, heading, rng,
            shouldTrim = this.shouldTrim(range);

                range.deleteContents();

                marker = this._insertMarker(doc, range);

                li = dom.closestEditableOfType(marker, ['li']);
                heading = dom.closestEditableOfType(marker, 'h1,h2,h3,h4,h5,h6'.split(','));

                if (li) {
                    // hitting 'enter' in empty li
                    if (dom.emptyNode(li)) {
                        paragraph = dom.create(doc, 'p');

                        if (li.nextSibling) {
                            rng = range.cloneRange();
                            rng.selectNode(li);

                            RangeUtils.split(rng, li.parentNode);
                        }

                        dom.insertAfter(paragraph, li.parentNode);
                        dom.remove(li.parentNode.childNodes.length == 1 ? li.parentNode : li);
                        paragraph.innerHTML = emptyParagraphContent;
                        next = paragraph;
                    }
                } else if (heading && this._blankAfter(marker)) {
                    paragraph = dom.create(doc, 'p');

                    dom.insertAfter(paragraph, heading);
                    paragraph.innerHTML = emptyParagraphContent;
                    dom.remove(marker);
                    next = paragraph;
                }

                if (!next) {
                    if (!(li || heading)) {
                        new BlockFormatter([{ tags: ['p']}]).apply([marker]);
                    }

                    range.selectNode(marker);

                    parent = dom.parentOfType(marker, [li ? 'li' : heading ? dom.name(heading) : 'p']);

                    RangeUtils.split(range, parent, shouldTrim);

                    previous = parent.previousSibling;

                    if (dom.is(previous, 'li') && previous.firstChild && !dom.is(previous.firstChild, 'br')) {
                        previous = previous.firstChild;
                    }

                    next = parent.nextSibling;

                    if (dom.is(next, 'li') && next.firstChild && !dom.is(next.firstChild, 'br')) {
                        next = next.firstChild;
                    }

                    dom.remove(parent);

                    this.clean(previous);
                    this.clean(next, { links: true });

                    // normalize updates the caret display in Gecko
                    normalize(previous);
                }

                normalize(next);

                this._moveFocus(range, next);

                range.collapse(true);

                dom.scrollTo(next);

                RangeUtils.selectRange(range);
            },

            clean: function (node, options) {
                var root = node;

                if (node.firstChild && dom.is(node.firstChild, 'br')) {
                    dom.remove(node.firstChild);
                }

                if (dom.isDataNode(node) && !node.nodeValue) {
                    node = node.parentNode;
                }

                if (node) {
                    while (node.firstChild && node.firstChild.nodeType == 1) {
                        node = node.firstChild;
                    }

                    if (!dom.isEmpty(node) && /^\s*$/.test(node.innerHTML)) {
                        node.innerHTML = editorNS.emptyElementContent;
                    }

                    if (options && options.links) {
                        while (node != root) {
                            if (dom.is(node, "a") && dom.emptyNode(node)) {
                                dom.unwrap(node);
                                break;
                            }
                            node = node.parentNode;
                        }
                    }
                }
            }
        });

        var NewLineCommand = Command.extend({
            init: function (options) {
                this.options = options;
                Command.fn.init.call(this, options);
            },

            exec: function () {
                var range = this.getRange();
                var br = dom.create(RangeUtils.documentFromRange(range), 'br');
                var filler;
                var browser = kendo.support.browser;
                var oldIE = browser.msie && browser.version < 11;

                range.deleteContents();
                range.insertNode(br);

                normalize(br.parentNode);

                if (!oldIE && (!br.nextSibling || dom.isWhitespace(br.nextSibling))) {
                    // Gecko and WebKit cannot put the caret after only one br.
                    filler = br.cloneNode(true);
                    filler.className = 'k-br';
                    dom.insertAfter(filler, br);
                }

                range.setStartAfter(br);
                range.collapse(true);

                dom.scrollTo(br.nextSibling || br);

                RangeUtils.selectRange(range);
            }
        });

        extend(editorNS, {
            ParagraphCommand: ParagraphCommand,
            NewLineCommand: NewLineCommand
        });

        registerTool("insertLineBreak", new Tool({ key: 13, shift: true, command: NewLineCommand }));
        registerTool("insertParagraph", new Tool({ key: 13, command: ParagraphCommand }));

    })(window.kendo.jQuery);

    (function ($) {

        // Imports ================================================================
        var kendo = window.kendo,
    Class = kendo.Class,
    extend = $.extend,
    Editor = kendo.ui.editor,
    dom = Editor.Dom,
    RangeUtils = Editor.RangeUtils,
    EditorUtils = Editor.EditorUtils,
    Command = Editor.Command,
    ToolTemplate = Editor.ToolTemplate,
    FormatTool = Editor.FormatTool,
    BlockFormatFinder = Editor.BlockFormatFinder,
    textNodes = RangeUtils.textNodes,
    registerTool = Editor.EditorUtils.registerTool;

        var ListFormatFinder = BlockFormatFinder.extend({
            init: function (tag) {
                this.tag = tag;
                var tags = this.tags = [tag == 'ul' ? 'ol' : 'ul', tag];

                BlockFormatFinder.fn.init.call(this, [{ tags: tags}]);
            },

            isFormatted: function (nodes) {
                var formatNodes = [], formatNode;

                for (var i = 0; i < nodes.length; i++) {
                    if ((formatNode = this.findFormat(nodes[i])) && dom.name(formatNode) == this.tag) {
                        formatNodes.push(formatNode);
                    }
                }

                if (formatNodes.length < 1) {
                    return false;
                }

                if (formatNodes.length != nodes.length) {
                    return false;
                }

                // check if sequential lists are selected
                for (i = 0; i < formatNodes.length; i++) {
                    if (formatNodes[i].parentNode != formatNode.parentNode) {
                        break;
                    }

                    if (formatNodes[i] != formatNode) {
                        return false;
                    }
                }

                return true;
            },

            findSuitable: function (nodes) {
                var candidate = dom.parentOfType(nodes[0], this.tags);

                if (candidate && dom.name(candidate) == this.tag) {
                    return candidate;
                }

                return null;
            }

        });

        var ListFormatter = Class.extend({
            init: function (tag, unwrapTag) {
                var that = this;
                that.finder = new ListFormatFinder(tag);
                that.tag = tag;
                that.unwrapTag = unwrapTag;
            },

            isList: function (node) {
                var name = dom.name(node);

                return name == "ul" || name == "ol" || name == "dl";
            },

            wrap: function (list, nodes) {
                var li = dom.create(list.ownerDocument, "li"),
            i, node;

                for (i = 0; i < nodes.length; i++) {
                    node = nodes[i];

                    if (dom.is(node, 'li')) {
                        list.appendChild(node);
                        continue;
                    }

                    if (this.isList(node)) {
                        while (node.firstChild) {
                            list.appendChild(node.firstChild);
                        }
                        continue;
                    }

                    if (dom.is(node, "td")) {
                        while (node.firstChild) {
                            li.appendChild(node.firstChild);
                        }
                        list.appendChild(li);
                        node.appendChild(list);
                        list = list.cloneNode(false);
                        li = li.cloneNode(false);
                        continue;
                    }

                    li.appendChild(node);

                    if (dom.isBlock(node)) {
                        list.appendChild(li);
                        dom.unwrap(node);
                        li = li.cloneNode(false);
                    }
                }

                if (li.firstChild) {
                    list.appendChild(li);
                }
            },

            containsAny: function (parent, nodes) {
                for (var i = 0; i < nodes.length; i++) {
                    if (dom.isAncestorOrSelf(parent, nodes[i])) {
                        return true;
                    }
                }

                return false;
            },

            suitable: function (candidate, nodes) {
                if (candidate.className == "k-marker") {
                    var sibling = candidate.nextSibling;

                    if (sibling && dom.isBlock(sibling)) {
                        return false;
                    }

                    sibling = candidate.previousSibling;

                    if (sibling && dom.isBlock(sibling)) {
                        return false;
                    }
                }

                return this.containsAny(candidate, nodes) || dom.isInline(candidate) || candidate.nodeType == 3;
            },

            split: function (range) {
                var nodes = textNodes(range),
            start, end;

                if (nodes.length) {
                    start = dom.parentOfType(nodes[0], ['li']);
                    end = dom.parentOfType(nodes[nodes.length - 1], ['li']);
                    range.setStartBefore(start);
                    range.setEndAfter(end);

                    for (var i = 0, l = nodes.length; i < l; i++) {
                        var formatNode = this.finder.findFormat(nodes[i]);
                        if (formatNode) {
                            var parents = $(formatNode).parents("ul,ol");
                            if (parents[0]) {
                                RangeUtils.split(range, parents.last()[0], true);
                            } else {
                                RangeUtils.split(range, formatNode, true);
                            }
                        }
                    }
                }
            },

            merge: function (tag, formatNode) {
                var prev = formatNode.previousSibling, next;

                while (prev && (prev.className == "k-marker" || (prev.nodeType == 3 && dom.isWhitespace(prev)))) {
                    prev = prev.previousSibling;
                }

                // merge with previous list
                if (prev && dom.name(prev) == tag) {
                    while (formatNode.firstChild) {
                        prev.appendChild(formatNode.firstChild);
                    }
                    dom.remove(formatNode);
                    formatNode = prev;
                }

                next = formatNode.nextSibling;
                while (next && (next.className == "k-marker" || (next.nodeType == 3 && dom.isWhitespace(next)))) {
                    next = next.nextSibling;
                }

                // merge with next list
                if (next && dom.name(next) == tag) {
                    while (formatNode.lastChild) {
                        next.insertBefore(formatNode.lastChild, next.firstChild);
                    }
                    dom.remove(formatNode);
                }
            },

            breakable: function (node) {
                return (
            node != node.ownerDocument.body &&
            !/table|tbody|tr|td/.test(dom.name(node)) &&
            !node.attributes.contentEditable
        );
            },

            applyOnSection: function (section, nodes) {
                var tag = this.tag;
                var commonAncestor = dom.closestSplittableParent(nodes);

                var ancestors = [];

                var formatNode = this.finder.findSuitable(nodes);

                if (!formatNode) {
                    formatNode = new ListFormatFinder(tag == "ul" ? "ol" : "ul").findSuitable(nodes);
                }

                var childNodes;

                if (/table|tbody/.test(dom.name(commonAncestor))) {
                    childNodes = $.map(nodes, function (node) {
                        return dom.parentOfType(node, ["td"]);
                    });
                } else {
                    childNodes = dom.significantChildNodes(commonAncestor);

                    if ($.grep(childNodes, dom.isBlock).length) {
                        childNodes = $.grep(childNodes, $.proxy(function (node) {
                            return this.containsAny(node, nodes);
                        }, this));
                    }

                    if (!childNodes.length) {
                        childNodes = nodes;
                    }
                }

                function pushAncestor() {
                    ancestors.push(this);
                }

                for (var i = 0; i < childNodes.length; i++) {
                    var child = childNodes[i];
                    var suitable = (!formatNode || !dom.isAncestorOrSelf(formatNode, child)) && this.suitable(child, nodes);

                    if (!suitable) {
                        continue;
                    }

                    if (formatNode && this.isList(child)) {
                        // merging lists
                        $.each(child.childNodes, pushAncestor);
                        dom.remove(child);
                    } else {
                        ancestors.push(child);
                    }
                }

                if (ancestors.length == childNodes.length && this.breakable(commonAncestor)) {
                    ancestors = [commonAncestor];
                }

                if (!formatNode) {
                    formatNode = dom.create(commonAncestor.ownerDocument, tag);
                    dom.insertBefore(formatNode, ancestors[0]);
                }

                this.wrap(formatNode, ancestors);

                if (!dom.is(formatNode, tag)) {
                    dom.changeTag(formatNode, tag);
                }

                this.merge(tag, formatNode);
            },

            apply: function (nodes) {
                var i = 0,
            sections = [],
            lastSection,
            lastNodes,
            section;

                // split nodes into sections that need to be different lists
                do {
                    section = dom.closestEditable(nodes[i], ["td", "body"]);

                    if (!lastSection || section != lastSection) {
                        if (lastSection) {
                            sections.push({
                                section: lastSection,
                                nodes: lastNodes
                            });
                        }

                        lastNodes = [nodes[i]];
                        lastSection = section;
                    } else {
                        lastNodes.push(nodes[i]);
                    }

                    i++;
                } while (i < nodes.length);

                sections.push({
                    section: lastSection,
                    nodes: lastNodes
                });

                for (i = 0; i < sections.length; i++) {
                    this.applyOnSection(sections[i].section, sections[i].nodes);
                }
            },

            unwrap: function (ul) {
                var fragment = ul.ownerDocument.createDocumentFragment(),
            unwrapTag = this.unwrapTag,
            parents,
            li,
            p,
            child;

                for (li = ul.firstChild; li; li = li.nextSibling) {
                    p = dom.create(ul.ownerDocument, unwrapTag || 'p');

                    while (li.firstChild) {
                        child = li.firstChild;

                        if (dom.isBlock(child)) {

                            if (p.firstChild) {
                                fragment.appendChild(p);
                                p = dom.create(ul.ownerDocument, unwrapTag || 'p');
                            }

                            fragment.appendChild(child);
                        } else {
                            p.appendChild(child);
                        }
                    }

                    if (p.firstChild) {
                        fragment.appendChild(p);
                    }
                }

                parents = $(ul).parents('ul,ol');

                if (parents[0]) {
                    dom.insertAfter(fragment, parents.last()[0]);
                    parents.last().remove();
                } else {
                    dom.insertAfter(fragment, ul);
                }

                dom.remove(ul);
            },

            remove: function (nodes) {
                var formatNode;
                for (var i = 0, l = nodes.length; i < l; i++) {
                    formatNode = this.finder.findFormat(nodes[i]);

                    if (formatNode) {
                        this.unwrap(formatNode);
                    }
                }
            },

            toggle: function (range) {
                var that = this,
            nodes = textNodes(range),
            ancestor = range.commonAncestorContainer;

                if (!nodes.length) {
                    range.selectNodeContents(ancestor);
                    nodes = textNodes(range);
                    if (!nodes.length) {
                        var text = ancestor.ownerDocument.createTextNode("");
                        range.startContainer.appendChild(text);
                        nodes = [text];
                        range.selectNode(text.parentNode);
                    }
                }

                if (that.finder.isFormatted(nodes)) {
                    that.split(range);
                    that.remove(nodes);
                } else {
                    that.apply(nodes);
                }
            }

        });

        var ListCommand = Command.extend({
            init: function (options) {
                options.formatter = new ListFormatter(options.tag);
                Command.fn.init.call(this, options);
            }
        });

        var ListTool = FormatTool.extend({
            init: function (options) {
                this.options = options;
                FormatTool.fn.init.call(this, extend(options, {
                    finder: new ListFormatFinder(options.tag)
                }));
            },

            command: function (commandArguments) {
                return new ListCommand(extend(commandArguments, { tag: this.options.tag }));
            }
        });

        extend(Editor, {
            ListFormatFinder: ListFormatFinder,
            ListFormatter: ListFormatter,
            ListCommand: ListCommand,
            ListTool: ListTool
        });

        registerTool("insertUnorderedList", new ListTool({ tag: 'ul', template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Insert unordered list" }) }));
        registerTool("insertOrderedList", new ListTool({ tag: 'ol', template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Insert ordered list" }) }));

    })(window.kendo.jQuery);

    (function ($, undefined) {

        var kendo = window.kendo,
    Class = kendo.Class,
    extend = $.extend,
    proxy = $.proxy,
    Editor = kendo.ui.editor,
    dom = Editor.Dom,
    RangeUtils = Editor.RangeUtils,
    EditorUtils = Editor.EditorUtils,
    Command = Editor.Command,
    Tool = Editor.Tool,
    ToolTemplate = Editor.ToolTemplate,
    InlineFormatter = Editor.InlineFormatter,
    InlineFormatFinder = Editor.InlineFormatFinder,
    textNodes = RangeUtils.textNodes,
    registerTool = Editor.EditorUtils.registerTool;

        var LinkFormatFinder = Class.extend({
            findSuitable: function (sourceNode) {
                return dom.parentOfType(sourceNode, ["a"]);
            }
        });

        var LinkFormatter = Class.extend({
            init: function () {
                this.finder = new LinkFormatFinder();
            },

            apply: function (range, attributes) {
                var nodes = textNodes(range);
                var markers;
                var doc;
                var formatter;
                var a;
                var parent;

                if (attributes.innerHTML) {
                    markers = RangeUtils.getMarkers(range);

                    doc = RangeUtils.documentFromRange(range);

                    range.deleteContents();
                    a = dom.create(doc, "a", attributes);
                    range.insertNode(a);

                    parent = a.parentNode;
                    if (dom.name(parent) == "a") {
                        dom.insertAfter(a, parent);
                    }

                    if (dom.emptyNode(parent)) {
                        dom.remove(parent);
                    }

                    if (markers.length > 1) {
                        dom.insertAfter(markers[markers.length - 1], a);
                        dom.insertAfter(markers[1], a);
                        dom[nodes.length > 0 ? "insertBefore" : "insertAfter"](markers[0], a);
                    }
                } else {
                    formatter = new InlineFormatter([{ tags: ["a"]}], attributes);
                    formatter.finder = this.finder;
                    formatter.apply(nodes);
                }
            }
        });

        var UnlinkCommand = Command.extend({
            init: function (options) {
                options.formatter = /** @ignore */{
                toggle: function (range) {
                    new InlineFormatter([{ tags: ["a"]}]).remove(textNodes(range));
                }
            };
            this.options = options;
            Command.fn.init.call(this, options);
        }
    });

    var LinkCommand = Command.extend({
        init: function (options) {
            var cmd = this;
            cmd.options = options;
            Command.fn.init.call(cmd, options);
            cmd.formatter = new LinkFormatter();
            if (!options.url) {
                cmd.attributes = null;
                cmd.async = true;
            } else {
                this.exec = function () {
                    this.formatter.apply(options.range, {
                        href: options.url,
                        innerHTML: options.text || options.url,
                        target: options.target
                    });
                };
            }
        },

        _dialogTemplate: function () {
            return kendo.template(
            '<div class="k-editor-dialog k-popup-edit-form k-edit-form-container">' +
                "<div class='k-edit-label'>" +
                    "<label for='k-editor-link-url'>#: messages.linkWebAddress #</label>" +
                "</div>" +
                "<div class='k-edit-field'>" +
                    "<input type='text' class='k-input k-textbox' id='k-editor-link-url'>" +
                "</div>" +
                "<div class='k-edit-label k-editor-link-text-row'>" +
                    "<label for='k-editor-link-text'>#: messages.linkText #</label>" +
                "</div>" +
                "<div class='k-edit-field k-editor-link-text-row'>" +
                    "<input type='text' class='k-input k-textbox' id='k-editor-link-text'>" +
                "</div>" +
                "<div class='k-edit-label'>" +
                    "<label for='k-editor-link-title'>#: messages.linkToolTip #</label>" +
                "</div>" +
                "<div class='k-edit-field'>" +
                    "<input type='text' class='k-input k-textbox' id='k-editor-link-title'>" +
                "</div>" +
                "<div class='k-edit-label'></div>" +
                "<div class='k-edit-field'>" +
                    "<input type='checkbox' class='k-checkbox' id='k-editor-link-target'>" +
                    "<label for='k-editor-link-target'>#: messages.linkOpenInNewWindow #</label>" +
                "</div>" +
                "<div class='k-edit-buttons k-state-default'>" +
                    '<button class="k-dialog-insert k-button k-primary">#: messages.dialogInsert #</button>' +
                    '<button class="k-dialog-close k-button">#: messages.dialogCancel #</button>' +
                "</div>" +
            "</div>"
        )({
            messages: this.editor.options.messages
        });
        },

        exec: function () {
            var collapsed = this.getRange().collapsed;
            var messages = this.editor.options.messages;

            this._initialText = "";
            this._range = this.lockRange(true);
            var nodes = textNodes(this._range);

            var a = nodes.length ? this.formatter.finder.findSuitable(nodes[0]) : null;
            var img = nodes.length && dom.name(nodes[0]) == "img";

            var dialog = this.createDialog(this._dialogTemplate(), {
                title: messages.createLink,
                close: proxy(this._close, this),
                visible: false
            });

            dialog
            .find(".k-dialog-insert").click(proxy(this._apply, this)).end()
            .find(".k-dialog-close").click(proxy(this._close, this)).end()
            .find(".k-edit-field input").keydown(proxy(this._keydown, this)).end()
            .find("#k-editor-link-url").val(this.linkUrl(a)).end()
            .find("#k-editor-link-text").val(this.linkText(nodes)).end()
            .find("#k-editor-link-title").val(a ? a.title : "").end()
            .find("#k-editor-link-target").attr("checked", a ? a.target == "_blank" : false).end()
            .find(".k-editor-link-text-row").toggle(!img);


            if (nodes.length > 0 && !collapsed) {
                this._initialText = $("#k-editor-link-text", dialog).val();
            }

            this._dialog = dialog.data("kendoWindow").center().open();

            $("#k-editor-link-url", dialog).focus().select();
        },

        _keydown: function (e) {
            var keys = kendo.keys;

            if (e.keyCode == keys.ENTER) {
                this._apply(e);
            } else if (e.keyCode == keys.ESC) {
                this._close(e);
            }
        },

        _apply: function (e) {
            var element = this._dialog.element;
            var href = $("#k-editor-link-url", element).val();
            var title, text, target;
            var textInput = $("#k-editor-link-text", element);

            if (href && href != "http://") {

                if (href.indexOf("@") > 0 && !/^(\w+:)|(\/\/)/i.test(href)) {
                    href = "mailto:" + href;
                }

                this.attributes = { href: href };

                title = $("#k-editor-link-title", element).val();
                if (title) {
                    this.attributes.title = title;
                }

                if (textInput.is(":visible")) {
                    text = textInput.val();
                    if (!text && !this._initialText) {
                        this.attributes.innerHTML = href;
                    } else if (text && (text !== this._initialText)) {
                        this.attributes.innerHTML = dom.stripBom(text);
                    }
                }

                target = $("#k-editor-link-target", element).is(":checked");
                this.attributes.target = target ? "_blank" : null;

                this.formatter.apply(this._range, this.attributes);
            }

            this._close(e);

            if (this.change) {
                this.change();
            }
        },

        _close: function (e) {
            e.preventDefault();
            this._dialog.destroy();

            dom.windowFromDocument(RangeUtils.documentFromRange(this._range)).focus();

            this.releaseRange(this._range);
        },

        linkUrl: function (anchor) {
            if (anchor) {
                // IE < 8 returns absolute url if getAttribute is not used
                return anchor.getAttribute("href", 2);
            }

            return "http://";
        },

        linkText: function (nodes) {
            var text = "";

            if (nodes.length == 1) {
                text = nodes[0].nodeValue;
            } else if (nodes.length) {
                text = nodes[0].nodeValue + nodes[1].nodeValue;
            }

            return dom.stripBom(text || "");
        },

        redo: function () {
            var range = this.lockRange(true);

            this.formatter.apply(range, this.attributes);
            this.releaseRange(range);
        }

    });

    var UnlinkTool = Tool.extend({
        init: function (options) {
            this.options = options;
            this.finder = new InlineFormatFinder([{ tags: ["a"]}]);

            Tool.fn.init.call(this, $.extend(options, { command: UnlinkCommand }));
        },

        initialize: function (ui, options) {
            Tool.fn.initialize.call(this, ui, options);
            ui.addClass("k-state-disabled");
        },

        update: function (ui, nodes) {
            ui.toggleClass("k-state-disabled", !this.finder.isFormatted(nodes))
          .removeClass("k-state-hover");
        }
    });

    extend(kendo.ui.editor, {
        LinkFormatFinder: LinkFormatFinder,
        LinkFormatter: LinkFormatter,
        UnlinkCommand: UnlinkCommand,
        LinkCommand: LinkCommand,
        UnlinkTool: UnlinkTool
    });

    registerTool("createLink", new Tool({ key: "K", ctrl: true, command: LinkCommand, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Create Link" }) }));
    registerTool("unlink", new UnlinkTool({ key: "K", ctrl: true, shift: true, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Remove Link" }) }));

})(window.kendo.jQuery);

(function ($, undefined) {

    var kendo = window.kendo,
    extend = $.extend,
    Editor = kendo.ui.editor,
    EditorUtils = Editor.EditorUtils,
    dom = Editor.Dom,
    registerTool = EditorUtils.registerTool,
    ToolTemplate = Editor.ToolTemplate,
    RangeUtils = Editor.RangeUtils,
    Command = Editor.Command,
    LinkFormatter = Editor.LinkFormatter,
    textNodes = RangeUtils.textNodes,
    keys = kendo.keys,
    KEDITORFILEURL = "#k-editor-file-url",
    KEDITORFILETITLE = "#k-editor-file-title";

    var FileCommand = Command.extend({
        init: function (options) {
            var that = this;
            Command.fn.init.call(that, options);

            that.formatter = new LinkFormatter();

            that.async = true;
            that.attributes = {};
        },

        insertFile: function (file, range) {
            var attributes = this.attributes;
            var doc = RangeUtils.documentFromRange(range);

            if (attributes.href && attributes.href != "http://") {

                if (!file) {
                    file = dom.create(doc, "a", { href: attributes.href });
                    file.innerHTML = attributes.innerHTML;

                    range.deleteContents();
                    range.insertNode(file);

                    if (!file.nextSibling) {
                        dom.insertAfter(doc.createTextNode("\ufeff"), file);
                    }

                    range.setStartAfter(file);
                    range.setEndAfter(file);
                    RangeUtils.selectRange(range);
                    return true;
                } else {
                    dom.attr(file, attributes);
                }
            }

            return false;
        },

        _dialogTemplate: function (showBrowser) {
            return kendo.template(
            '<div class="k-editor-dialog k-popup-edit-form k-edit-form-container">' +
                '# if (showBrowser) { #' +
                    '<div class="k-filebrowser"></div>' +
                '# } #' +
                "<div class='k-edit-label'>" +
                    '<label for="k-editor-file-url">#: messages.fileWebAddress #</label>' +
                "</div>" +
                "<div class='k-edit-field'>" +
                    '<input type="text" class="k-input k-textbox" id="k-editor-file-url">' +
                "</div>" +
                "<div class='k-edit-label'>" +
                    '<label for="k-editor-file-title">#: messages.fileTitle #</label>' +
                "</div>" +
                "<div class='k-edit-field'>" +
                    '<input type="text" class="k-input k-textbox" id="k-editor-file-title">' +
                "</div>" +
                '<div class="k-edit-buttons k-state-default">' +
                    '<button class="k-dialog-insert k-button k-primary">#: messages.dialogInsert #</button>' +
                    '<button class="k-dialog-close k-button">#: messages.dialogCancel #</button>' +
                '</div>' +
            '</div>'
        )({
            messages: this.editor.options.messages,
            showBrowser: showBrowser
        });
        },

        redo: function () {
            var that = this,
            range = that.lockRange();

            this.formatter.apply(range, this.attributes);
            that.releaseRange(range);
        },

        exec: function () {
            var that = this,
            range = that.lockRange(),
            nodes = textNodes(range),
            applied = false,
            file = nodes.length ? this.formatter.finder.findSuitable(nodes[0]) : null,
            dialog,
            options = that.editor.options,
            messages = options.messages,
            fileBrowser = options.fileBrowser,
            showBrowser = !!(kendo.ui.FileBrowser && fileBrowser && fileBrowser.transport && fileBrowser.transport.read !== undefined),
            dialogOptions = {
                title: messages.insertFile,
                visible: false,
                resizable: showBrowser
            };

            function apply(e) {
                var element = dialog.element,
                href = element.find(KEDITORFILEURL).val().replace(/ /g, "%20"),
                innerHTML = element.find(KEDITORFILETITLE).val();

                that.attributes = {
                    href: href,
                    innerHTML: innerHTML !== "" ? innerHTML : href
                };

                applied = that.insertFile(file, range);

                close(e);

                if (that.change) {
                    that.change();
                }
            }

            function close(e) {
                e.preventDefault();
                dialog.destroy();

                dom.windowFromDocument(RangeUtils.documentFromRange(range)).focus();
                if (!applied) {
                    that.releaseRange(range);
                }
            }

            function keyDown(e) {
                if (e.keyCode == keys.ENTER) {
                    apply(e);
                } else if (e.keyCode == keys.ESC) {
                    close(e);
                }
            }

            dialogOptions.close = close;

            if (showBrowser) {
                dialogOptions.width = 750;
            }

            dialog = this.createDialog(that._dialogTemplate(showBrowser), dialogOptions)
            .toggleClass("k-filebrowser-dialog", showBrowser)
            .find(".k-dialog-insert").click(apply).end()
            .find(".k-dialog-close").click(close).end()
            .find(".k-edit-field input").keydown(keyDown).end()
            // IE < 8 returns absolute url if getAttribute is not used
            .find(KEDITORFILEURL).val(file ? file.getAttribute("href", 2) : "http://").end()
            .find(KEDITORFILETITLE).val(file ? file.title : "").end()
            .data("kendoWindow");

            if (showBrowser) {
                new kendo.ui.FileBrowser(
                dialog.element.find(".k-filebrowser"),
                extend({}, fileBrowser, {
                    change: function () {
                        dialog.element.find(KEDITORFILEURL).val(this.value());
                    },
                    apply: apply
                })
            );
            }

            dialog.center().open();
            dialog.element.find(KEDITORFILEURL).focus().select();
        }

    });

    kendo.ui.editor.FileCommand = FileCommand;

    registerTool("insertFile", new Editor.Tool({ command: FileCommand, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Insert File" }) }));

})(window.kendo.jQuery);

(function ($, undefined) {

    var kendo = window.kendo,
    extend = $.extend,
    Editor = kendo.ui.editor,
    EditorUtils = Editor.EditorUtils,
    dom = Editor.Dom,
    registerTool = EditorUtils.registerTool,
    ToolTemplate = Editor.ToolTemplate,
    RangeUtils = Editor.RangeUtils,
    Command = Editor.Command,
    keys = kendo.keys,
    KEDITORIMAGEURL = "#k-editor-image-url",
    KEDITORIMAGETITLE = "#k-editor-image-title",
    KEDITORIMAGEWIDTH = "#k-editor-image-width",
    KEDITORIMAGEHEIGHT = "#k-editor-image-height";

    var ImageCommand = Command.extend({
        init: function (options) {
            var that = this;
            Command.fn.init.call(that, options);

            that.async = true;
            that.attributes = {};
        },

        insertImage: function (img, range) {
            var attributes = this.attributes;
            var doc = RangeUtils.documentFromRange(range);

            if (attributes.src && attributes.src != "http://") {

                var removeIEAttributes = function () {
                    setTimeout(function () {
                        if (!attributes.width) {
                            img.removeAttribute("width");
                        }

                        if (!attributes.height) {
                            img.removeAttribute("height");
                        }

                        img.removeAttribute("complete");
                    });
                };

                if (!img) {
                    img = dom.create(doc, "img", attributes);
                    img.onload = img.onerror = function () {
                        removeIEAttributes();
                    };

                    range.deleteContents();
                    range.insertNode(img);

                    if (!img.nextSibling) {
                        dom.insertAfter(doc.createTextNode("\ufeff"), img);
                    }

                    removeIEAttributes();

                    range.setStartAfter(img);
                    range.setEndAfter(img);
                    RangeUtils.selectRange(range);
                    return true;
                } else {
                    dom.attr(img, attributes);
                    removeIEAttributes();
                }
            }

            return false;
        },

        _dialogTemplate: function (showBrowser) {
            return kendo.template(
            '<div class="k-editor-dialog k-popup-edit-form k-edit-form-container">' +
                '# if (showBrowser) { #' +
                    '<div class="k-filebrowser k-imagebrowser"></div>' +
                '# } #' +
                "<div class='k-edit-label'>" +
                    '<label for="k-editor-image-url">#: messages.imageWebAddress #</label>' +
                "</div>" +
                "<div class='k-edit-field'>" +
                    '<input type="text" class="k-input k-textbox" id="k-editor-image-url">' +
                "</div>" +
                "<div class='k-edit-label'>" +
                    '<label for="k-editor-image-title">#: messages.imageAltText #</label>' +
                "</div>" +
                "<div class='k-edit-field'>" +
                    '<input type="text" class="k-input k-textbox" id="k-editor-image-title">' +
                "</div>" +
                "<div class='k-edit-label'>" +
                    '<label for="k-editor-image-width">#: messages.imageWidth #</label>' +
                "</div>" +
                "<div class='k-edit-field'>" +
                    '<input type="text" class="k-input k-textbox" id="k-editor-image-width">' +
                "</div>" +
                "<div class='k-edit-label'>" +
                    '<label for="k-editor-image-height">#: messages.imageHeight #</label>' +
                "</div>" +
                "<div class='k-edit-field'>" +
                    '<input type="text" class="k-input k-textbox" id="k-editor-image-height">' +
                "</div>" +
                '<div class="k-edit-buttons k-state-default">' +
                    '<button class="k-dialog-insert k-button k-primary">#: messages.dialogInsert #</button>' +
                    '<button class="k-dialog-close k-button">#: messages.dialogCancel #</button>' +
                '</div>' +
            '</div>'
        )({
            messages: this.editor.options.messages,
            showBrowser: showBrowser
        });
        },

        redo: function () {
            var that = this,
            range = that.lockRange();

            if (!that.insertImage(RangeUtils.image(range), range)) {
                that.releaseRange(range);
            }
        },

        exec: function () {
            var that = this,
            range = that.lockRange(),
            applied = false,
            img = RangeUtils.image(range),
            imageWidth = img && img.getAttribute("width") || "",
            imageHeight = img && img.getAttribute("height") || "",
            dialog,
            options = that.editor.options,
            messages = options.messages,
            imageBrowser = options.imageBrowser,
            showBrowser = !!(kendo.ui.ImageBrowser && imageBrowser && imageBrowser.transport && imageBrowser.transport.read !== undefined),
            dialogOptions = {
                title: messages.insertImage,
                visible: false,
                resizable: showBrowser
            };

            function apply(e) {
                var element = dialog.element,
                w = parseInt(element.find(KEDITORIMAGEWIDTH).val(), 10),
                h = parseInt(element.find(KEDITORIMAGEHEIGHT).val(), 10);

                that.attributes = {
                    src: element.find(KEDITORIMAGEURL).val().replace(/ /g, "%20"),
                    alt: element.find(KEDITORIMAGETITLE).val()
                };

                that.attributes.width = null;
                that.attributes.height = null;

                if (!isNaN(w) && w > 0) {
                    that.attributes.width = w;
                }

                if (!isNaN(h) && h > 0) {
                    that.attributes.height = h;
                }

                applied = that.insertImage(img, range);

                close(e);

                if (that.change) {
                    that.change();
                }
            }

            function close(e) {
                e.preventDefault();
                dialog.destroy();

                dom.windowFromDocument(RangeUtils.documentFromRange(range)).focus();
                if (!applied) {
                    that.releaseRange(range);
                }
            }

            function keyDown(e) {
                if (e.keyCode == keys.ENTER) {
                    apply(e);
                } else if (e.keyCode == keys.ESC) {
                    close(e);
                }
            }

            dialogOptions.close = close;

            if (showBrowser) {
                dialogOptions.width = 750;
            }

            dialog = this.createDialog(that._dialogTemplate(showBrowser), dialogOptions)
            .toggleClass("k-filebrowser-dialog", showBrowser)
            .find(".k-dialog-insert").click(apply).end()
            .find(".k-dialog-close").click(close).end()
            .find(".k-edit-field input").keydown(keyDown).end()
            // IE < 8 returns absolute url if getAttribute is not used
            .find(KEDITORIMAGEURL).val(img ? img.getAttribute("src", 2) : "http://").end()
            .find(KEDITORIMAGETITLE).val(img ? img.alt : "").end()
            .find(KEDITORIMAGEWIDTH).val(imageWidth).end()
            .find(KEDITORIMAGEHEIGHT).val(imageHeight).end()
            .data("kendoWindow");

            if (showBrowser) {
                new kendo.ui.ImageBrowser(
                dialog.element.find(".k-imagebrowser"),
                extend({}, imageBrowser, {
                    change: function () {
                        dialog.element.find(KEDITORIMAGEURL).val(this.value());
                    },
                    apply: apply
                })
            );
            }

            dialog.center().open();
            dialog.element.find(KEDITORIMAGEURL).focus().select();
        }

    });

    kendo.ui.editor.ImageCommand = ImageCommand;

    registerTool("insertImage", new Editor.Tool({ command: ImageCommand, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Insert Image" }) }));

})(window.kendo.jQuery);

(function ($, undefined) {

    var kendo = window.kendo,
    DropDownList = kendo.ui.DropDownList,
    dom = kendo.ui.editor.Dom;

    var SelectBox = DropDownList.extend({
        init: function (element, options) {
            var that = this;

            DropDownList.fn.init.call(that, element, options);

            // overlay drop-down with popout for snappier interaction
            if (kendo.support.mobileOS.ios) {
                this._initSelectOverlay();
                this.bind("dataBound", $.proxy(this._initSelectOverlay, this));
            }

            that.value(that.options.title);

            that.bind("open", function () {
                if (that.options.autoSize) {
                    var list = that.list,
                    listWidth;

                    list.css({
                        whiteSpace: "nowrap",
                        width: "auto"
                    });

                    listWidth = list.width();

                    if (listWidth) {
                        listWidth += 20;
                    } else {
                        listWidth = that._listWidth;
                    }

                    list.css("width", listWidth + kendo.support.scrollbar());

                    that._listWidth = listWidth;
                }
            });
        },
        options: {
            name: "SelectBox"
        },

        _initSelectOverlay: function () {
            var selectBox = this;
            var value = selectBox.value();
            var view = this.dataSource.view();
            var item;
            var html = "";
            var encode = kendo.htmlEncode;

            for (var i = 0; i < view.length; i++) {
                item = view[i];

                html += "<option value='" + encode(item.value) + "'";

                if (item.value == value) {
                    html += " selected";
                }

                html += ">" + encode(item.text) + "</option>";
            }

            var select = $("<select class='k-select-overlay'>" + html + "</select>");
            var wrapper = $(this.element).closest(".k-widget");

            wrapper.next(".k-select-overlay").remove();

            select.insertAfter(wrapper);

            select.on("change", function () {
                selectBox.value(this.value);
                selectBox.trigger("change");
            });
        },

        value: function (value) {
            var that = this,
            result = DropDownList.fn.value.call(that, value);

            if (value === undefined) {
                return result;
            }

            if (value !== DropDownList.fn.value.call(that)) {
                that.text(that.options.title);

                if (that._current) {
                    that._current.removeClass("k-state-selected");
                }

                that.current(null);
                that._oldIndex = that.selectedIndex = -1;
            }
        },

        decorate: function (body) {
            var that = this,
            dataSource = that.dataSource,
            items = dataSource.data(),
            i, tag, className, style;

            if (body) {
                that.list.css("background-color", dom.getEffectiveBackground($(body)));
            }

            for (i = 0; i < items.length; i++) {
                tag = items[i].tag || "span";
                className = items[i].className;

                style = dom.inlineStyle(body, tag, { className: className });

                style = style.replace(/"/g, "'");

                items[i].style = style + ";display:inline-block";
            }

            dataSource.trigger("change");
        }
    });


    kendo.ui.plugin(SelectBox);
    kendo.ui.editor.SelectBox = SelectBox;

})(window.kendo.jQuery);

(function ($, undefined) {

    // Imports ================================================================
    var kendo = window.kendo,
    Class = kendo.Class,
    extend = $.extend,
    Editor = kendo.ui.editor,
    dom = Editor.Dom,
    EditorUtils = Editor.EditorUtils,
    registerTool = EditorUtils.registerTool,
    Command = Editor.Command,
    Tool = Editor.Tool,
    ToolTemplate = Editor.ToolTemplate,
    RangeUtils = Editor.RangeUtils,
    blockElements = dom.blockElements,
    BlockFormatFinder = Editor.BlockFormatFinder,
    BlockFormatter = Editor.BlockFormatter;

    function indent(node, value) {
        var isRtl = $(node).css("direction") == "rtl",
        indentDirection = isRtl ? "Right" : "Left",
        property = dom.name(node) != "td" ? "margin" + indentDirection : "padding" + indentDirection;
        if (value === undefined) {
            return node.style[property] || 0;
        } else {
            if (value > 0) {
                node.style[property] = value + "px";
            } else {
                node.style[property] = "";

                if (!node.style.cssText) {
                    node.removeAttribute("style");
                }
            }
        }
    }

    var IndentFormatter = Class.extend({
        init: function () {
            this.finder = new BlockFormatFinder([{ tags: dom.blockElements}]);
        },

        apply: function (nodes) {
            var formatNodes = this.finder.findSuitable(nodes),
            targets = [],
            i, len, formatNode, parentList, sibling;

            if (formatNodes.length) {
                for (i = 0, len = formatNodes.length; i < len; i++) {
                    if (dom.is(formatNodes[i], "li")) {
                        if (!$(formatNodes[i]).index()) {
                            targets.push(formatNodes[i].parentNode);
                        } else if ($.inArray(formatNodes[i].parentNode, targets) < 0) {
                            targets.push(formatNodes[i]);
                        }
                    } else {
                        targets.push(formatNodes[i]);
                    }
                }

                while (targets.length) {
                    formatNode = targets.shift();
                    if (dom.is(formatNode, "li")) {
                        parentList = formatNode.parentNode;
                        sibling = $(formatNode).prev("li");
                        var siblingList = sibling.find("ul,ol").last();

                        var nestedList = $(formatNode).children("ul,ol")[0];

                        if (nestedList && sibling[0]) {
                            if (siblingList[0]) {
                                siblingList.append(formatNode);
                                siblingList.append($(nestedList).children());
                                dom.remove(nestedList);
                            } else {
                                sibling.append(nestedList);
                                nestedList.insertBefore(formatNode, nestedList.firstChild);
                            }
                        } else {
                            nestedList = sibling.children("ul,ol")[0];
                            if (!nestedList) {
                                nestedList = dom.create(formatNode.ownerDocument, dom.name(parentList));
                                sibling.append(nestedList);
                            }

                            while (formatNode && formatNode.parentNode == parentList) {
                                nestedList.appendChild(formatNode);
                                formatNode = targets.shift();
                            }
                        }
                    } else {
                        var marginLeft = parseInt(indent(formatNode), 10) + 30;
                        indent(formatNode, marginLeft);

                        for (var targetIndex = 0; targetIndex < targets.length; targetIndex++) {
                            if ($.contains(formatNode, targets[targetIndex])) {
                                targets.splice(targetIndex, 1);
                            }
                        }
                    }
                }
            } else {
                var formatter = new BlockFormatter([{ tags: ["p"]}], { style: { marginLeft: 30} });

                formatter.apply(nodes);
            }
        },

        remove: function (nodes) {
            var formatNodes = this.finder.findSuitable(nodes),
            targetNode, i, len, list, listParent, siblings,
            formatNode, marginLeft;

            for (i = 0, len = formatNodes.length; i < len; i++) {
                formatNode = $(formatNodes[i]);

                if (formatNode.is("li")) {
                    list = formatNode.parent();
                    listParent = list.parent();
                    // listParent will be ul or ol in case of invalid dom - <ul><li></li><ul><li></li></ul></ul>
                    if (listParent.is("li,ul,ol") && !indent(list[0])) {
                        // skip already processed nodes
                        if (targetNode && $.contains(targetNode, listParent[0])) {
                            continue;
                        }

                        siblings = formatNode.nextAll("li");
                        if (siblings.length) {
                            $(list[0].cloneNode(false)).appendTo(formatNode).append(siblings);
                        }

                        if (listParent.is("li")) {
                            formatNode.insertAfter(listParent);
                        } else {
                            formatNode.appendTo(listParent);
                        }

                        if (!list.children("li").length) {
                            list.remove();
                        }

                        continue;
                    } else {
                        if (targetNode == list[0]) {
                            // removing format on sibling LI elements
                            continue;
                        }
                        targetNode = list[0];
                    }
                } else {
                    targetNode = formatNodes[i];
                }

                marginLeft = parseInt(indent(targetNode), 10) - 30;
                indent(targetNode, marginLeft);
            }
        }

    });

    var IndentCommand = Command.extend({
        init: function (options) {
            options.formatter = /** @ignore */{
            toggle: function (range) {
                new IndentFormatter().apply(RangeUtils.nodes(range));
            }
        };
        Command.fn.init.call(this, options);
    }
});

var OutdentCommand = Command.extend({
    init: function (options) {
        options.formatter = {
            toggle: function (range) {
                new IndentFormatter().remove(RangeUtils.nodes(range));
            }
        };
        Command.fn.init.call(this, options);
    }
});

var OutdentTool = Tool.extend({
    init: function (options) {
        Tool.fn.init.call(this, options);

        this.finder = new BlockFormatFinder([{ tags: blockElements}]);
    },

    initialize: function (ui, options) {
        Tool.fn.initialize.call(this, ui, options);
        ui.addClass("k-state-disabled");
    },

    update: function (ui, nodes) {
        var suitable = this.finder.findSuitable(nodes),
            isOutdentable, listParentsCount, i, len;

        for (i = 0, len = suitable.length; i < len; i++) {
            isOutdentable = indent(suitable[i]);

            if (!isOutdentable) {
                listParentsCount = $(suitable[i]).parents("ul,ol").length;
                isOutdentable = (dom.is(suitable[i], "li") && (listParentsCount > 1 || indent(suitable[i].parentNode))) ||
                                (dom.ofType(suitable[i], ["ul", "ol"]) && listParentsCount > 0);
            }

            if (isOutdentable) {
                ui.removeClass("k-state-disabled");
                return;
            }
        }

        ui.addClass("k-state-disabled").removeClass("k-state-hover");
    }
});

extend(Editor, {
    IndentFormatter: IndentFormatter,
    IndentCommand: IndentCommand,
    OutdentCommand: OutdentCommand,
    OutdentTool: OutdentTool
});

registerTool("indent", new Tool({ command: IndentCommand, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Indent" }) }));
registerTool("outdent", new OutdentTool({ command: OutdentCommand, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Outdent" }) }));

})(window.kendo.jQuery);

(function ($, undefined) {

    var kendo = window.kendo,
    extend = $.extend,
    Editor = kendo.ui.editor,
    EditorUtils = Editor.EditorUtils,
    Command = Editor.Command,
    Tool = Editor.Tool,
    ToolTemplate = Editor.ToolTemplate;

    var ViewHtmlCommand = Command.extend({
        init: function (options) {
            var cmd = this;
            cmd.options = options;
            Command.fn.init.call(cmd, options);
            cmd.attributes = null;
            cmd.async = true;
        },

        exec: function () {
            var that = this,
            editor = that.editor,
            messages = editor.options.messages,
            dialog = $(kendo.template(ViewHtmlCommand.template)(messages)).appendTo(document.body),
            content = ViewHtmlCommand.indent(editor.value()),
            textarea = ".k-editor-textarea";

            function apply(e) {
                editor.value(dialog.find(textarea).val());

                close(e);

                if (that.change) {
                    that.change();
                }

                editor.trigger("change");
            }

            function close(e) {
                e.preventDefault();

                dialog.data("kendoWindow").destroy();

                editor.focus();
            }

            this.createDialog(dialog, {
                title: messages.viewHtml,
                close: close,
                visible: false
            })
            .find(textarea).val(content).end()
            .find(".k-dialog-update").click(apply).end()
            .find(".k-dialog-close").click(close).end()
            .data("kendoWindow").center().open();

            dialog.find(textarea).focus();
        }
    });

    extend(ViewHtmlCommand, {
        template: "<div class='k-editor-dialog k-popup-edit-form k-edit-form-container k-viewhtml-dialog'>" +
                "<textarea class='k-editor-textarea k-input'></textarea>" +
                "<div class='k-edit-buttons k-state-default'>" +
                    "<button class='k-dialog-update k-button k-primary'>#: dialogUpdate #</button>" +
                    "<button class='k-dialog-close k-button'>#: dialogCancel #</button>" +
                "</div>" +
            "</div>",
        indent: function (content) {
            return content.replace(/<\/(p|li|ul|ol|h[1-6]|table|tr|td|th)>/ig, "</$1>\n")
                      .replace(/<(ul|ol)([^>]*)><li/ig, "<$1$2>\n<li")
                      .replace(/<br \/>/ig, "<br />\n")
                      .replace(/\n$/, "");
        }
    });

    kendo.ui.editor.ViewHtmlCommand = ViewHtmlCommand;

    Editor.EditorUtils.registerTool("viewHtml", new Tool({ command: ViewHtmlCommand, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "View HTML" }) }));

})(window.kendo.jQuery);

(function ($) {

    var kendo = window.kendo,
    Editor = kendo.ui.editor,
    Tool = Editor.Tool,
    ToolTemplate = Editor.ToolTemplate,
    DelayedExecutionTool = Editor.DelayedExecutionTool,
    Command = Editor.Command,
    dom = Editor.Dom,
    EditorUtils = Editor.EditorUtils,
    RangeUtils = Editor.RangeUtils,
    registerTool = EditorUtils.registerTool;


    var FormattingTool = DelayedExecutionTool.extend({
        init: function (options) {
            var that = this;
            Tool.fn.init.call(that, kendo.deepExtend({}, that.options, options));

            that.type = "kendoSelectBox";

            that.finder = {
                getFormat: function () { return ""; }
            };
        },

        options: {
            items: [
            { text: "Paragraph", value: "p" },
            { text: "Quotation", value: "blockquote" },
            { text: "Heading 1", value: "h1" },
            { text: "Heading 2", value: "h2" },
            { text: "Heading 3", value: "h3" },
            { text: "Heading 4", value: "h4" },
            { text: "Heading 5", value: "h5" },
            { text: "Heading 6", value: "h6" }
        ],
            width: 110
        },

        toFormattingItem: function (item) {
            var value = item.value;

            if (!value) {
                return item;
            }

            if (item.tag || item.className) {
                return item;
            }

            var dot = value.indexOf(".");

            if (dot === 0) {
                item.className = value.substring(1);
            } else if (dot == -1) {
                item.tag = value;
            } else {
                item.tag = value.substring(0, dot);
                item.className = value.substring(dot + 1);
            }

            return item;
        },

        command: function (args) {
            var item = args.value;

            item = this.toFormattingItem(item);

            return new Editor.FormatCommand({
                range: args.range,
                formatter: function () {
                    var formatter,
                    tags = (item.tag || item.context || "span").split(","),
                    format = [{
                        tags: tags,
                        attr: { className: item.className || "" }
                    }];

                    if ($.inArray(tags[0], dom.inlineElements) >= 0) {
                        formatter = new Editor.GreedyInlineFormatter(format);
                    } else {
                        formatter = new Editor.GreedyBlockFormatter(format);
                    }

                    return formatter;
                }
            });
        },

        initialize: function (ui, initOptions) {
            var editor = initOptions.editor;
            var options = this.options;
            var toolName = options.name;
            var that = this;

            ui.width(options.width);

            ui.kendoSelectBox({
                dataTextField: "text",
                dataValueField: "value",
                dataSource: options.items || editor.options[toolName],
                title: editor.options.messages[toolName],
                autoSize: true,
                change: function () {
                    Tool.exec(editor, toolName, this.dataItem().toJSON());
                },
                dataBound: function () {
                    var i, items = this.dataSource.data();

                    for (i = 0; i < items.length; i++) {
                        items[i] = that.toFormattingItem(items[i]);
                    }
                },
                highlightFirst: false,
                template: kendo.template(
                '<span unselectable="on" style="display:block;#=(data.style||"")#">#:data.text#</span>'
            )
            });

            ui.addClass("k-decorated")
            .closest(".k-widget")
                .removeClass("k-" + toolName)
                .find("*").addBack()
                    .attr("unselectable", "on");
        },

        getFormattingValue: function (items, nodes) {
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                var tag = item.tag || item.context || "";
                var className = item.className ? "." + item.className : "";
                var selector = tag + className;

                var element = $(nodes[0]).closest(selector)[0];

                if (!element) {
                    continue;
                }

                if (nodes.length == 1) {
                    return item.value;
                }

                for (var n = 1; n < nodes.length; n++) {
                    if (!$(nodes[n]).closest(selector)[0]) {
                        break;
                    } else if (n == nodes.length - 1) {
                        return item.value;
                    }
                }
            }

            return "";
        },

        update: function (ui, nodes) {
            var selectBox = $(ui).data(this.type);

            // necessary until formatBlock is deleted
            if (!selectBox) {
                return;
            }

            var dataSource = selectBox.dataSource,
            items = dataSource.data(),
            i, context,
            ancestor = dom.commonAncestor.apply(null, nodes);

            for (i = 0; i < items.length; i++) {
                context = items[i].context;

                items[i].visible = !context || !!$(ancestor).closest(context).length;
            }

            dataSource.filter([{ field: "visible", operator: "eq", value: true}]);

            DelayedExecutionTool.fn.update.call(this, ui, nodes);

            selectBox.value(this.getFormattingValue(dataSource.view(), nodes));

            selectBox.wrapper.toggleClass("k-state-disabled", !dataSource.view().length);
        }
    });

    var CleanFormatCommand = Command.extend({
        exec: function () {
            var range = this.lockRange(true);
            var remove = this.options.remove || "strong,em,span".split(",");

            RangeUtils.wrapSelectedElements(range);

            var iterator = new Editor.RangeIterator(range);

            iterator.traverse(function clean(node) {
                if (!node || dom.isMarker(node)) {
                    return;
                }

                if (node.nodeType == 1 && !dom.insignificant(node)) {
                    for (var i = node.childNodes.length - 1; i >= 0; i--) {
                        clean(node.childNodes[i]);
                    }

                    node.removeAttribute("style");
                    node.removeAttribute("class");
                }

                if (remove.indexOf(dom.name(node)) > -1) {
                    dom.unwrap(node);
                }
            });

            this.releaseRange(range);
        }
    });

    $.extend(Editor, {
        FormattingTool: FormattingTool,
        CleanFormatCommand: CleanFormatCommand
    });

    registerTool("formatting", new FormattingTool({ template: new ToolTemplate({ template: EditorUtils.dropDownListTemplate, title: "Format" }) }));
    registerTool("cleanFormatting", new Tool({ command: CleanFormatCommand, template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Clean formatting" }) }));

})(window.kendo.jQuery);

(function ($, undefined) {
    var kendo = window.kendo;
    var ui = kendo.ui;
    var editorNS = ui.editor;
    var Widget = ui.Widget;
    var extend = $.extend;
    var proxy = $.proxy;
    var keys = kendo.keys;
    var NS = ".kendoEditor";

    var focusable = "a.k-tool:not(.k-state-disabled)," +
                    ".k-widget.k-colorpicker,.k-selectbox,.k-dropdown,.k-combobox .k-input";

    var Toolbar = Widget.extend({
        init: function (element, options) {
            var that = this;

            options = extend({}, options, { name: "EditorToolbar" });

            Widget.fn.init.call(that, element, options);

            if (options.popup) {
                that._initPopup();
            }
        },

        events: [
            "execute"
        ],

        groups: {
            basic: ["bold", "italic", "underline", "strikethrough"],
            scripts: ["subscript", "superscript"],
            alignment: ["justifyLeft", "justifyCenter", "justifyRight", "justifyFull"],
            links: ["insertImage", "insertFile", "createLink", "unlink"],
            lists: ["insertUnorderedList", "insertOrderedList", "indent", "outdent"],
            tables: ["createTable", "addColumnLeft", "addColumnRight", "addRowAbove", "addRowBelow", "deleteRow", "deleteColumn"],
            advanced: ["viewHtml", "cleanFormatting"]
        },

        _initPopup: function () {
            this.window = $(this.element)
                .wrap("<div class='editorToolbarWindow k-header' />")
                .parent()
                .prepend("<button class='k-button k-button-bare k-editortoolbar-dragHandle'><span class='k-icon k-i-move' /></button>")
                .kendoWindow({
                    title: false,
                    resizable: false,
                    draggable: {
                        dragHandle: ".k-editortoolbar-dragHandle"
                    },
                    animation: {
                        open: { effects: "fade:in" },
                        close: { effects: "fade:out" }
                    },
                    minHeight: 42,
                    visible: false,
                    autoFocus: false,
                    actions: [],
                    dragend: function () {
                        this._moved = true;
                    }
                })
                .on("mousedown", function (e) {
                    if (!$(e.target).is(".k-icon")) {
                        e.preventDefault();
                    }
                })
                .data("kendoWindow");
        },

        items: function () {
            return this.element.children().find("> *, select");
        },

        focused: function () {
            return this.element.find(".k-state-focused").length > 0;
        },

        toolById: function (name) {
            var id, tools = this.tools;

            for (id in tools) {
                if (id.toLowerCase() == name) {
                    return tools[id];
                }
            }
        },

        toolGroupFor: function (toolName) {
            var i, groups = this.groups;

            if (this.isCustomTool(toolName)) {
                return "custom";
            }

            for (i in groups) {
                if ($.inArray(toolName, groups[i]) >= 0) {
                    return i;
                }
            }
        },

        bindTo: function (editor) {
            var that = this,
                window = that.window;

            // detach from editor that was previously listened to
            if (that._editor) {
                that._editor.unbind("select", proxy(that.refreshTools, that));
            }

            that._editor = editor;

            // re-initialize the tools
            that.tools = that.expandTools(editor.options.tools);
            that.render();

            that.element.find(".k-combobox .k-input").keydown(function (e) {
                var combobox = $(this).closest(".k-combobox").data("kendoComboBox"),
                    key = e.keyCode;

                if (key == keys.RIGHT || key == keys.LEFT) {
                    combobox.close();
                } else if (key == keys.DOWN) {
                    if (!combobox.dropDown.isOpened()) {
                        e.stopImmediatePropagation();
                        combobox.open();
                    }
                }
            });

            that._attachEvents();

            that.items().each(function initializeTool() {
                var toolName = that._toolName(this),
                    tool = that.tools[toolName],
                    options = tool && tool.options,
                    messages = editor.options.messages,
                    description = options && options.tooltip || messages[toolName],
                    ui = $(this);

                if (!tool || !tool.initialize) {
                    return;
                }

                if (toolName == "fontSize" || toolName == "fontName") {
                    var inheritText = messages[toolName + "Inherit"];

                    ui.find("input").val(inheritText).end()
                      .find("span.k-input").text(inheritText).end();
                }

                tool.initialize(ui, {
                    title: that._appendShortcutSequence(description, tool),
                    editor: that._editor
                });

                ui.closest(".k-widget", that.element).addClass("k-editor-widget");

                ui.closest(".k-colorpicker", that.element).next(".k-colorpicker").addClass("k-editor-widget");
            });

            editor.bind("select", proxy(that.refreshTools, that));

            that.update();

            if (window) {
                window.wrapper.css({ top: "", left: "", width: "" });
            }
        },

        show: function () {
            var that = this,
                window = that.window,
                editorOptions = that.options.editor,
                wrapper, editorElement, editorOffset;

            if (window) {
                wrapper = window.wrapper;
                editorElement = editorOptions.element;

                if (!wrapper.is(":visible") || !that.window.options.visible) {

                    if (!wrapper[0].style.width) {
                        wrapper.width(editorElement.outerWidth() - parseInt(wrapper.css("border-left-width"), 10) - parseInt(wrapper.css("border-right-width"), 10));
                    }

                    // track content position when other parts of page change
                    if (!window._moved) {
                        editorOffset = editorElement.offset();
                        wrapper.css({
                            top: Math.max(0, parseInt(editorOffset.top, 10) - wrapper.outerHeight() - parseInt(that.window.element.css("padding-bottom"), 10)),
                            left: Math.max(0, parseInt(editorOffset.left, 10))
                        });
                    }

                    window.open();
                }
            }
        },

        hide: function () {
            if (this.window) {
                this.window.close();
            }
        },

        focus: function () {
            var TABINDEX = "tabIndex";
            var element = this.element;
            var tabIndex = this._editor.element.attr(TABINDEX);

            // Chrome can't focus something which has already been focused
            element.attr(TABINDEX, tabIndex || 0).focus()
                .find(focusable).first().focus();

            if (!tabIndex && tabIndex !== 0) {
                element.removeAttr(TABINDEX);
            }
        },

        _appendShortcutSequence: function (localizedText, tool) {
            if (!tool.key) {
                return localizedText;
            }

            var res = localizedText + " (";

            if (tool.ctrl) {
                res += "Ctrl + ";
            }

            if (tool.shift) {
                res += "Shift + ";
            }

            if (tool.alt) {
                res += "Alt + ";
            }

            res += tool.key + ")";

            return res;
        },

        _nativeTools: [
            "insertLineBreak",
            "insertParagraph",
            "redo",
            "undo"
        ],

        tools: {}, // tools collection is copied from defaultTools during initialization

        isCustomTool: function (toolName) {
            return !(toolName in kendo.ui.Editor.defaultTools);
        },

        // expand the tools parameter to contain tool options objects
        expandTools: function (tools) {
            var currentTool,
                i,
                nativeTools = this._nativeTools,
                options,
                defaultTools = kendo.deepExtend({}, kendo.ui.Editor.defaultTools),
                result = {},
                name;

            for (i = 0; i < tools.length; i++) {
                currentTool = tools[i];
                name = currentTool.name;

                if ($.isPlainObject(currentTool)) {
                    if (name && defaultTools[name]) {
                        // configured tool
                        result[name] = extend({}, defaultTools[name]);
                        extend(result[name].options, currentTool);
                    } else {
                        // custom tool
                        options = extend({ cssClass: "k-i-custom", type: "button", title: "" }, currentTool);
                        if (!options.name) {
                            options.name = "custom";
                        }

                        options.cssClass = "k-" + (options.name == "custom" ? "i-custom" : options.name);

                        if (!options.template && options.type == "button") {
                            options.template = editorNS.EditorUtils.buttonTemplate;
                            options.title = options.title || options.tooltip;
                        }

                        result[name] = {
                            options: options
                        };
                    }
                } else if (defaultTools[currentTool]) {
                    // tool by name
                    result[currentTool] = defaultTools[currentTool];
                }
            }

            for (i = 0; i < nativeTools.length; i++) {
                if (!result[nativeTools[i]]) {
                    result[nativeTools[i]] = defaultTools[nativeTools[i]];
                }
            }

            return result;
        },

        render: function () {
            var that = this,
                tools = that.tools,
                options, template, toolElement,
                toolName,
                editorElement = that._editor.element,
                element = that.element.empty(),
                groupName, newGroupName,
                toolConfig = that._editor.options.tools,
                browser = kendo.support.browser,
                group, i;

            function stringify(template) {
                var result;

                if (template.getHtml) {
                    result = template.getHtml();
                } else {
                    if (!$.isFunction(template)) {
                        template = kendo.template(template);
                    }

                    result = template(options);
                }

                return $.trim(result);
            }

            function endGroup() {
                if (group.children().length) {
                    group.appendTo(element);
                }
            }

            function startGroup() {
                group = $("<li class='k-tool-group' role='presentation' />");
            }

            element.empty();

            startGroup();

            for (i = 0; i < toolConfig.length; i++) {
                toolName = toolConfig[i].name || toolConfig[i];
                options = tools[toolName] && tools[toolName].options;

                if (!options && $.isPlainObject(toolName)) {
                    options = toolName;
                }

                template = options && options.template;

                if (toolName == "break") {
                    endGroup();
                    $("<li class='k-row-break' />").appendTo(that.element);
                    startGroup();
                }

                if (!template) {
                    continue;
                }

                newGroupName = that.toolGroupFor(toolName);

                if (groupName != newGroupName) {
                    endGroup();
                    startGroup();
                    groupName = newGroupName;
                }

                template = stringify(template);

                toolElement = $(template).appendTo(group);

                if (newGroupName == "custom") {
                    endGroup();
                    startGroup();
                }

                if (options.exec && toolElement.hasClass("k-tool")) {
                    toolElement.click(proxy(options.exec, editorElement[0]));
                }
            }

            endGroup();

            $(that.element).children(":has(> .k-tool)").addClass("k-button-group");

            if (that.options.popup && browser.msie && browser.version < 9) {
                that.window.wrapper.find("*").attr("unselectable", "on");
            }

            that.updateGroups();

            that.angular("compile", function () {
                return { elements: that.element };
            });
        },

        updateGroups: function () {
            $(this.element).children().each(function () {
                $(this).children().filter(function () {
                    return this.style.display !== "none";
                })
                    .removeClass("k-group-end")
                    .first().addClass("k-group-start").end()
                    .last().addClass("k-group-end").end();
            });
        },

        decorateFrom: function (body) {
            this.items().filter(".k-decorated")
                .each(function () {
                    var selectBox = $(this).data("kendoSelectBox");

                    if (selectBox) {
                        selectBox.decorate(body);
                    }
                });
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            var id, tools = this.tools;

            for (id in tools) {
                if (tools[id].destroy) {
                    tools[id].destroy();
                }
            }

            if (this.window) {
                this.window.destroy();
            }
        },

        _attachEvents: function () {
            var that = this,
                buttons = "[role=button].k-tool",
                enabledButtons = buttons + ":not(.k-state-disabled)",
                disabledButtons = buttons + ".k-state-disabled";

            that.element
                .off(NS)
                .on("mouseenter" + NS, enabledButtons, function () { $(this).addClass("k-state-hover"); })
                .on("mouseleave" + NS, enabledButtons, function () { $(this).removeClass("k-state-hover"); })
                .on("mousedown" + NS, buttons, function (e) {
                    e.preventDefault();
                })
                .on("keydown" + NS, focusable, function (e) {
                    var current = this;
                    var focusElement,
                        keyCode = e.keyCode;

                    function move(direction, constrain) {
                        var tools = that.element.find(focusable);
                        var index = tools.index(current) + direction;

                        if (constrain) {
                            index = Math.max(0, Math.min(tools.length - 1, index));
                        }

                        return tools[index];
                    }

                    if (keyCode == keys.RIGHT || keyCode == keys.LEFT) {
                        if (!$(current).hasClass(".k-dropdown")) {
                            focusElement = move(keyCode == keys.RIGHT ? 1 : -1, true);
                        }
                    } else if (keyCode == keys.ESC) {
                        focusElement = that._editor;
                    } else if (keyCode == keys.TAB && !(e.ctrlKey || e.altKey)) {
                        // skip tabbing to disabled tools, and focus the editing area when running out of tools
                        if (e.shiftKey) {
                            focusElement = move(-1);
                        } else {
                            focusElement = move(1);

                            if (!focusElement) {
                                focusElement = that._editor;
                            }
                        }
                    }

                    if (focusElement) {
                        e.preventDefault();
                        focusElement.focus();
                    }
                })
                .on("click" + NS, enabledButtons, function (e) {
                    var button = $(this);
                    e.preventDefault();
                    e.stopPropagation();
                    button.removeClass("k-state-hover");
                    if (!button.is("[data-popup]")) {
                        that._editor.exec(that._toolName(this));
                    }
                })
                .on("click" + NS, disabledButtons, function (e) { e.preventDefault(); });

        },


        _toolName: function (element) {
            if (!element) {
                return;
            }

            var className = element.className;

            if (/k-tool\b/i.test(className)) {
                className = element.firstChild.className;
            }

            var tool = $.grep(className.split(" "), function (x) {
                return !/^k-(widget|tool|tool-icon|state-hover|header|combobox|dropdown|selectbox|colorpicker)$/i.test(x);
            });

            return tool[0] ? tool[0].substring(tool[0].lastIndexOf("-") + 1) : "custom";
        },

        refreshTools: function () {
            var that = this,
                editor = that._editor,
                range = editor.getRange(),
                nodes = kendo.ui.editor.RangeUtils.textNodes(range);

            if (!nodes.length) {
                nodes = [range.startContainer];
            }

            that.items().each(function () {
                var tool = that.tools[that._toolName(this)];
                if (tool && tool.update) {
                    tool.update($(this), nodes);
                }
            });

            this.update();
        },

        update: function () {
            this.element.children().children().each(function () {
                var tool = $(this);
                tool.css("display", tool.hasClass("k-state-disabled") ? "none" : "");
            });
            this.updateGroups();
        }
    });

    $.extend(editorNS, {
        Toolbar: Toolbar
    });

})(window.jQuery);

(function ($, undefined) {

    var kendo = window.kendo,
    extend = $.extend,
    proxy = $.proxy,
    Editor = kendo.ui.editor,
    dom = Editor.Dom,
    EditorUtils = Editor.EditorUtils,
    RangeUtils = Editor.RangeUtils,
    Command = Editor.Command,
    NS = ".kendoEditor",
    ACTIVESTATE = "k-state-active",
    SELECTEDSTATE = "k-state-selected",
    Tool = Editor.Tool,
    ToolTemplate = Editor.ToolTemplate,
    InsertHtmlCommand = Editor.InsertHtmlCommand,
    BlockFormatFinder = Editor.BlockFormatFinder,
    registerTool = Editor.EditorUtils.registerTool;

    var editableCell = "<td>" + Editor.emptyElementContent + "</td>";

    var tableFormatFinder = new BlockFormatFinder([{ tags: ["table"]}]);

    var TableCommand = InsertHtmlCommand.extend({
        _tableHtml: function (rows, columns) {
            rows = rows || 1;
            columns = columns || 1;

            return "<table class='k-table' data-last>" +
                   new Array(rows + 1).join("<tr>" + new Array(columns + 1).join(editableCell) + "</tr>") +
               "</table>";
        },

        postProcess: function (editor, range) {
            var insertedTable = $("table[data-last]", editor.document).removeAttr("data-last");

            range.selectNodeContents(insertedTable.find("td")[0]);

            editor.selectRange(range);
        },

        exec: function () {
            var options = this.options;
            options.html = this._tableHtml(options.rows, options.columns);
            options.postProcess = this.postProcess;

            InsertHtmlCommand.fn.exec.call(this);
        }
    });

    var PopupTool = Tool.extend({
        initialize: function (ui, options) {
            Tool.fn.initialize.call(this, ui, options);

            var popup = $(this.options.popupTemplate).appendTo("body").kendoPopup({
                anchor: ui,
                copyAnchorStyles: false,
                open: proxy(this._open, this),
                activate: proxy(this._activate, this),
                close: proxy(this._close, this)
            }).data("kendoPopup");

            ui.click(proxy(this._toggle, this))
          .keydown(proxy(this._keydown, this));

            this._editor = options.editor;
            this._popup = popup;
        },

        popup: function () {
            return this._popup;
        },

        _activate: $.noop,

        _open: function () {
            this._popup.options.anchor.addClass(ACTIVESTATE);
        },

        _close: function () {
            this._popup.options.anchor.removeClass(ACTIVESTATE);
        },

        _keydown: function (e) {
            var keys = kendo.keys;
            var key = e.keyCode;

            if (key == keys.DOWN && e.altKey) {
                this._popup.open();
            } else if (key == keys.ESC) {
                this._popup.close();
            }
        },

        _toggle: function (e) {
            var button = $(e.target).closest(".k-tool");

            if (!button.hasClass("k-state-disabled")) {
                this.popup().toggle();
            }
        },

        update: function (ui) {
            this.popup().close();

            ui.removeClass("k-state-hover");
        },

        destroy: function () {
            this._popup.destroy();
        }
    });

    var InsertTableTool = PopupTool.extend({
        init: function (options) {
            this.cols = 8;
            this.rows = 6;

            PopupTool.fn.init.call(this, $.extend(options, {
                command: TableCommand,
                popupTemplate:
                "<div class='k-ct-popup'>" +
                    new Array(this.cols * this.rows + 1).join("<span class='k-ct-cell k-state-disabled' />") +
                    "<div class='k-status'></div>" +
                "</div>"
            }));
        },

        _activate: function () {
            var that = this,
            element = that._popup.element,
            cells = element.find(".k-ct-cell"),
            firstCell = cells.eq(0),
            lastCell = cells.eq(cells.length - 1),
            start = kendo.getOffset(firstCell),
            end = kendo.getOffset(lastCell),
            cols = that.cols,
            rows = that.rows,
            cellWidth, cellHeight;

            end.left += lastCell[0].offsetWidth;
            end.top += lastCell[0].offsetHeight;

            cellWidth = (end.left - start.left) / cols;
            cellHeight = (end.top - start.top) / rows;

            function tableFromLocation(e) {
                var w = $(window);
                return {
                    row: Math.floor((e.clientY + w.scrollTop() - start.top) / cellHeight) + 1,
                    col: Math.floor((e.clientX + w.scrollLeft() - start.left) / cellWidth) + 1
                };
            }

            element
            .on("mousemove" + NS, function (e) {
                that._setTableSize(tableFromLocation(e));
            })
            .on("mouseleave" + NS, function () {
                that._setTableSize();
            })
            .on("mouseup" + NS, function (e) {
                that._exec(tableFromLocation(e));
            });
        },

        _valid: function (size) {
            return size && size.row > 0 && size.col > 0 && size.row <= this.rows && size.col <= this.cols;
        },

        _exec: function (size) {
            if (this._valid(size)) {
                this._editor.exec("createTable", {
                    rows: size.row,
                    columns: size.col
                });
                this._popup.close();
            }
        },

        _setTableSize: function (size) {
            var element = this._popup.element;
            var status = element.find(".k-status");
            var cells = element.find(".k-ct-cell");
            var rows = this.rows;
            var cols = this.cols;
            var messages = this._editor.options.messages;

            if (this._valid(size)) {
                status.text(kendo.format(messages.createTableHint, size.row, size.col));

                cells.each(function (i) {
                    $(this).toggleClass(
                    SELECTEDSTATE,
                    i % cols < size.col && i / cols < size.row
                );
                });
            } else {
                status.text(messages.dialogCancel);
                cells.removeClass(SELECTEDSTATE);
            }
        },

        _keydown: function (e) {
            PopupTool.fn._keydown.call(this, e);

            var keys = kendo.keys;
            var key = e.keyCode;
            var cells = this._popup.element.find(".k-ct-cell");
            var focus = Math.max(cells.filter(".k-state-selected").last().index(), 0);
            var selectedRows = Math.floor(focus / this.cols);
            var selectedColumns = focus % this.cols;

            var changed = false;

            if (key == keys.DOWN && !e.altKey) {
                changed = true;
                selectedRows++;
            } else if (key == keys.UP) {
                changed = true;
                selectedRows--;
            } else if (key == keys.RIGHT) {
                changed = true;
                selectedColumns++;
            } else if (key == keys.LEFT) {
                changed = true;
                selectedColumns--;
            }

            var tableSize = {
                row: Math.max(1, Math.min(this.rows, selectedRows + 1)),
                col: Math.max(1, Math.min(this.cols, selectedColumns + 1))
            };

            if (key == keys.ENTER) {
                this._exec(tableSize);
            } else {
                this._setTableSize(tableSize);
            }

            if (changed) {
                e.preventDefault();
                e.stopImmediatePropagation();
            }
        },

        _open: function () {
            var messages = this._editor.options.messages;

            PopupTool.fn._open.call(this);

            this.popup().element
            .find(".k-status").text(messages.dialogCancel).end()
            .find(".k-ct-cell").removeClass(SELECTEDSTATE);
        },

        _close: function () {
            PopupTool.fn._close.call(this);
            this.popup().element.off(NS);
        },

        update: function (ui, nodes) {
            var isFormatted;

            PopupTool.fn.update.call(this, ui);

            isFormatted = tableFormatFinder.isFormatted(nodes);
            ui.toggleClass("k-state-disabled", isFormatted);
        }
    });

    var InsertRowCommand = Command.extend({
        exec: function () {
            var range = this.lockRange(true),
            td = range.endContainer,
            cellCount, row,
            newRow;

            while (dom.name(td) != "td") {
                td = td.parentNode;
            }

            row = td.parentNode;
            cellCount = row.children.length;
            newRow = row.cloneNode(true);

            for (var i = 0; i < row.cells.length; i++) {
                newRow.cells[i].innerHTML = Editor.emptyElementContent;
            }

            if (this.options.position == "before") {
                dom.insertBefore(newRow, row);
            } else {
                dom.insertAfter(newRow, row);
            }

            this.releaseRange(range);
        }
    });

    var InsertColumnCommand = Command.extend({
        exec: function () {
            var range = this.lockRange(true),
            td = dom.closest(range.endContainer, "td"),
            table = dom.closest(td, "table"),
            columnIndex,
            i,
            rows = table.rows,
            cell,
            newCell,
            position = this.options.position;

            columnIndex = dom.findNodeIndex(td);

            for (i = 0; i < rows.length; i++) {
                cell = rows[i].cells[columnIndex];

                newCell = cell.cloneNode();
                newCell.innerHTML = Editor.emptyElementContent;

                if (position == "before") {
                    dom.insertBefore(newCell, cell);
                } else {
                    dom.insertAfter(newCell, cell);
                }
            }

            this.releaseRange(range);
        }
    });

    var DeleteRowCommand = Command.extend({
        exec: function () {
            var range = this.lockRange();
            var rows = RangeUtils.mapAll(range, function (node) {
                return $(node).closest("tr")[0];
            });
            var table = dom.closest(rows[0], "table");
            var focusElement;

            if (table.rows.length <= rows.length) {
                focusElement = dom.next(table);
                if (!focusElement || dom.insignificant(focusElement)) {
                    focusElement = dom.prev(table);
                }

                dom.remove(table);
            } else {
                for (var i = 0; i < rows.length; i++) {
                    var row = rows[i];
                    dom.removeTextSiblings(row);

                    focusElement = dom.next(row) || dom.prev(row);
                    focusElement = focusElement.cells[0];

                    dom.remove(row);
                }
            }

            if (focusElement) {
                range.setStart(focusElement, 0);
                range.collapse(true);
                this.editor.selectRange(range);
            }
        }
    });

    var DeleteColumnCommand = Command.extend({
        exec: function () {
            var range = this.lockRange(),
            td = dom.closest(range.endContainer, "td"),
            table = dom.closest(td, "table"),
            rows = table.rows,
            columnIndex = dom.findNodeIndex(td, true),
            columnCount = rows[0].cells.length,
            focusElement, i;

            if (columnCount == 1) {
                focusElement = dom.next(table);
                if (!focusElement || dom.insignificant(focusElement)) {
                    focusElement = dom.prev(table);
                }

                dom.remove(table);
            } else {
                dom.removeTextSiblings(td);

                focusElement = dom.next(td) || dom.prev(td);

                for (i = 0; i < rows.length; i++) {
                    dom.remove(rows[i].cells[columnIndex]);
                }
            }

            if (focusElement) {
                range.setStart(focusElement, 0);
                range.collapse(true);
                this.editor.selectRange(range);
            }
        }
    });

    var TableModificationTool = Tool.extend({
        command: function (options) {
            options = extend(options, this.options);

            if (options.action == "delete") {
                if (options.type == "row") {
                    return new DeleteRowCommand(options);
                } else {
                    return new DeleteColumnCommand(options);
                }
            } else {
                if (options.type == "row") {
                    return new InsertRowCommand(options);
                } else {
                    return new InsertColumnCommand(options);
                }
            }
        },

        initialize: function (ui, options) {
            Tool.fn.initialize.call(this, ui, options);
            ui.addClass("k-state-disabled");
        },

        update: function (ui, nodes) {
            var isFormatted = !tableFormatFinder.isFormatted(nodes);
            ui.toggleClass("k-state-disabled", isFormatted);
        }
    });

    extend(kendo.ui.editor, {
        PopupTool: PopupTool,
        TableCommand: TableCommand,
        InsertTableTool: InsertTableTool,
        TableModificationTool: TableModificationTool,
        InsertRowCommand: InsertRowCommand,
        InsertColumnCommand: InsertColumnCommand,
        DeleteRowCommand: DeleteRowCommand,
        DeleteColumnCommand: DeleteColumnCommand
    });

    registerTool("createTable", new InsertTableTool({ template: new ToolTemplate({ template: EditorUtils.buttonTemplate, popup: true, title: "Create table" }) }));

    registerTool("addColumnLeft", new TableModificationTool({ type: "column", position: "before", template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Add column on the left" }) }));
    registerTool("addColumnRight", new TableModificationTool({ type: "column", template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Add column on the right" }) }));
    registerTool("addRowAbove", new TableModificationTool({ type: "row", position: "before", template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Add row above" }) }));
    registerTool("addRowBelow", new TableModificationTool({ type: "row", template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Add row below" }) }));
    registerTool("deleteRow", new TableModificationTool({ type: "row", action: "delete", template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Delete row" }) }));
    registerTool("deleteColumn", new TableModificationTool({ type: "column", action: "delete", template: new ToolTemplate({ template: EditorUtils.buttonTemplate, title: "Delete column" }) }));
    //registerTool("mergeCells", new Tool({ template: new ToolTemplate({template: EditorUtils.buttonTemplate, title: "Merge cells"})}));

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo;
    var caret = kendo.caret;
    var keys = kendo.keys;
    var ui = kendo.ui;
    var Widget = ui.Widget;
    var ns = ".kendoMaskedTextBox";
    var proxy = $.proxy;

    var INPUT_EVENT_NAME = (kendo.support.propertyChangeEvent ? "propertychange" : "input") + ns;
    var STATEDISABLED = "k-state-disabled";
    var DISABLED = "disabled";
    var READONLY = "readonly";
    var CHANGE = "change";

    var MaskedTextBox = Widget.extend({
        init: function (element, options) {
            var that = this;
            var DOMElement;

            Widget.fn.init.call(that, element, options);

            that._rules = $.extend({}, that.rules, that.options.rules);

            element = that.element;
            DOMElement = element[0];

            that.wrapper = element;
            that._tokenize();
            that._reset();

            that.element
                .addClass("k-textbox")
                .attr("autocomplete", "off")
                .on("focus" + ns, function () {
                    var value = DOMElement.value;

                    if (!value) {
                        DOMElement.value = that._old = that._emptyMask;
                    } else {
                        that._togglePrompt(true);
                    }

                    that._oldValue = value;

                    that._timeoutId = setTimeout(function () {
                        caret(element, 0, value ? that._maskLength : 0);
                    });
                })
                .on("focusout" + ns, function () {
                    var value = element.val();

                    clearTimeout(that._timeoutId);
                    DOMElement.value = that._old = "";

                    if (value !== that._emptyMask) {
                        DOMElement.value = that._old = value;
                    }

                    that._change();
                    that._togglePrompt();
                });

            var disabled = element.is("[disabled]");

            if (disabled) {
                that.enable(false);
            } else {
                that.readonly(element.is("[readonly]"));
            }

            that.value(that.options.value || element.val());

            kendo.notify(that);
        },

        options: {
            name: "MaskedTextBox",
            promptChar: "_",
            clearPromptChar: false,
            culture: "",
            rules: {},
            value: "",
            mask: ""
        },

        events: [
            CHANGE
        ],

        rules: {
            "0": /\d/,
            "9": /\d|\s/,
            "#": /\d|\s|\+|\-/,
            "L": /[a-zA-Z]/,
            "?": /[a-zA-Z]|\s/,
            "&": /\S/,
            "C": /./,
            "A": /[a-zA-Z0-9]/,
            "a": /[a-zA-Z0-9]|\s/
        },

        setOptions: function (options) {
            var that = this;

            Widget.fn.setOptions.call(that, options);

            that._rules = $.extend({}, that.rules, that.options.rules);

            that._tokenize();

            this._unbindInput();
            this._bindInput();

            that.value(that.element.val());
        },

        destroy: function () {
            var that = this;

            that.element.off(ns);

            if (that._form) {
                that._form.off("reset", that._resetHandler);
            }

            Widget.fn.destroy.call(that);
        },

        value: function (value) {
            var element = this.element;
            var emptyMask = this._emptyMask;
            var options = this.options;

            if (value === undefined) {
                return this.element.val();
            }

            if (value === null) {
                value = "";
            }

            if (!emptyMask) {
                element.val(value);
                return;
            }

            value = this._unmask(value + "");

            element.val(value ? emptyMask : "");

            this._mask(0, this._maskLength, value);

            value = element.val();
            this._oldValue = value;

            if (kendo._activeElement() !== element) {
                if (value === emptyMask) {
                    element.val("");
                } else {
                    this._togglePrompt();
                }
            }
        },

        _togglePrompt: function (show) {
            var DOMElement = this.element[0];
            var value = DOMElement.value;

            if (this.options.clearPromptChar) {
                if (!show) {
                    value = value.replace(new RegExp(this.options.promptChar, "g"), " ");
                } else {
                    value = this._oldValue;
                }

                DOMElement.value = this._old = value;
            }
        },

        readonly: function (readonly) {
            this._editable({
                readonly: readonly === undefined ? true : readonly,
                disable: false
            });
        },

        enable: function (enable) {
            this._editable({
                readonly: false,
                disable: !(enable = enable === undefined ? true : enable)
            });
        },

        _bindInput: function () {
            var that = this;

            if (that._maskLength) {
                that.element
                    .on("keydown" + ns, proxy(that._keydown, that))
                    .on("keypress" + ns, proxy(that._keypress, that))
                    .on("paste" + ns, proxy(that._paste, that))
                    .on(INPUT_EVENT_NAME, proxy(that._propertyChange, that));
            }
        },

        _unbindInput: function () {
            this.element
                .off("keydown" + ns)
                .off("keypress" + ns)
                .off("paste" + ns)
                .off(INPUT_EVENT_NAME);
        },

        _editable: function (options) {
            var that = this;
            var element = that.element;
            var disable = options.disable;
            var readonly = options.readonly;

            that._unbindInput();

            if (!readonly && !disable) {
                element.removeAttr(DISABLED)
                       .removeAttr(READONLY)
                       .removeClass(STATEDISABLED);

                that._bindInput();
            } else {
                element.attr(DISABLED, disable)
                       .attr(READONLY, readonly)
                       .toggleClass(STATEDISABLED, disable);
            }
        },

        _change: function () {
            var that = this;
            var value = that.value();

            if (value !== that._oldValue) {
                that._oldValue = value;

                that.trigger(CHANGE);
                that.element.trigger(CHANGE);
            }
        },

        _propertyChange: function () {
            var that = this;
            var element = that.element[0];
            var value = element.value;
            var unmasked;
            var start;

            if (value !== that._old && !that._pasting) {
                start = caret(element)[0];
                unmasked = that._unmask(value.substring(start), start);

                element.value = that._old = value.substring(0, start) + that._emptyMask.substring(start);

                that._mask(start, start, unmasked);
                caret(element, start);
            }
        },

        _paste: function (e) {
            var that = this;
            var element = e.target;
            var position = caret(element);
            var start = position[0];
            var end = position[1];

            var unmasked = that._unmask(element.value.substring(end), end);

            that._pasting = true;

            setTimeout(function () {
                var value = element.value;
                var pasted = value.substring(start, caret(element)[0]);

                element.value = that._old = value.substring(0, start) + that._emptyMask.substring(start);

                that._mask(start, start, pasted);

                start = caret(element)[0];

                that._mask(start, start, unmasked);

                caret(element, start);

                that._pasting = false;
            });
        },

        _reset: function () {
            var that = this;
            var element = that.element;
            var formId = element.attr("form");
            var form = formId ? $("#" + formId) : element.closest("form");

            if (form[0]) {
                that._resetHandler = function () {
                    setTimeout(function () {
                        that.value(element[0].value);
                    });
                };

                that._form = form.on("reset", that._resetHandler);
            }
        },

        _keydown: function (e) {
            var key = e.keyCode;
            var element = this.element[0];
            var selection = caret(element);
            var start = selection[0];
            var end = selection[1];
            var placeholder;

            var backward = key === keys.BACKSPACE;

            if (backward || key === keys.DELETE) {
                if (start === end) {
                    if (backward) {
                        start -= 1;
                    } else {
                        end += 1;
                    }

                    placeholder = this._find(start, backward);
                }

                if (placeholder !== undefined && placeholder !== start) {
                    if (backward) {
                        placeholder += 1;
                    }

                    caret(element, placeholder);
                } else if (start > -1) {
                    this._mask(start, end, "", backward);
                }

                e.preventDefault();
            } else if (key === keys.ENTER) {
                this._change();
            }
        },

        _keypress: function (e) {
            if (e.which === 0 || e.ctrlKey || e.keyCode === keys.ENTER) {
                return;
            }

            var character = String.fromCharCode(e.which);

            var selection = caret(this.element);

            this._mask(selection[0], selection[1], character);

            if (e.keyCode === keys.BACKSPACE || character) {
                e.preventDefault();
            }
        },

        _find: function (idx, backward) {
            var value = this.element.val() || this._emptyMask;
            var step = 1;

            if (backward === true) {
                step = -1;
            }

            while (idx > -1 || idx <= this._maskLength) {
                if (value.charAt(idx) !== this.tokens[idx]) {
                    return idx;
                }

                idx += step;
            }

            return -1;
        },

        _mask: function (start, end, value, backward) {
            var element = this.element[0];
            var current = element.value || this._emptyMask;
            var empty = this.options.promptChar;
            var valueLength;
            var chrIdx = 0;
            var unmasked;
            var chr;
            var idx;

            start = this._find(start, backward);

            if (start > end) {
                end = start;
            }

            unmasked = this._unmask(current.substring(end), end);
            value = this._unmask(value, start);
            valueLength = value.length;

            if (value) {
                unmasked = unmasked.replace(new RegExp("^_{0," + valueLength + "}"), "");
            }

            value += unmasked;
            current = current.split("");
            chr = value.charAt(chrIdx);

            while (start < this._maskLength) {
                current[start] = chr || empty;
                chr = value.charAt(++chrIdx);

                if (idx === undefined && chrIdx > valueLength) {
                    idx = start;
                }

                start = this._find(start + 1);
            }

            element.value = this._old = current.join("");

            if (kendo._activeElement() === element) {
                if (idx === undefined) {
                    idx = this._maskLength;
                }

                caret(element, idx);
            }
        },

        _unmask: function (value, idx) {
            if (!value) {
                return "";
            }

            value = (value + "").split("");

            var chr;
            var token;
            var chrIdx = 0;
            var tokenIdx = idx || 0;

            var empty = this.options.promptChar;

            var valueLength = value.length;
            var tokensLength = this.tokens.length;

            var result = "";

            while (tokenIdx < tokensLength) {
                chr = value[chrIdx];
                token = this.tokens[tokenIdx];

                if (chr === token || chr === empty) {
                    result += chr === empty ? empty : "";

                    chrIdx += 1;
                    tokenIdx += 1;
                } else if (typeof token !== "string") {
                    if ((token.test && token.test(chr)) || ($.isFunction(token) && token(chr))) {
                        result += chr;
                        tokenIdx += 1;
                    }

                    chrIdx += 1;
                } else {
                    tokenIdx += 1;
                }

                if (chrIdx >= valueLength) {
                    break;
                }
            }

            return result;
        },

        _tokenize: function () {
            var tokens = [];
            var tokenIdx = 0;

            var mask = this.options.mask || "";
            var maskChars = mask.split("");
            var length = maskChars.length;
            var idx = 0;
            var chr;
            var rule;

            var emptyMask = "";
            var promptChar = this.options.promptChar;
            var numberFormat = kendo.getCulture(this.options.culture).numberFormat;
            var rules = this._rules;

            for (; idx < length; idx++) {
                chr = maskChars[idx];
                rule = rules[chr];

                if (rule) {
                    tokens[tokenIdx] = rule;
                    emptyMask += promptChar;
                    tokenIdx += 1;
                } else {
                    if (chr === "." || chr === ",") {
                        chr = numberFormat[chr];
                    } else if (chr === "$") {
                        chr = numberFormat.currency.symbol;
                    } else if (chr === "\\") {
                        idx += 1;
                        chr = maskChars[idx];
                    }

                    chr = chr.split("");

                    for (var i = 0, l = chr.length; i < l; i++) {
                        tokens[tokenIdx] = chr[i];
                        emptyMask += chr[i];
                        tokenIdx += 1;
                    }
                }
            }

            this.tokens = tokens;

            this._emptyMask = emptyMask;
            this._maskLength = emptyMask.length;
        }
    });

    ui.plugin(MaskedTextBox);

})(window.kendo.jQuery);





/*jshint eqnull: true*/
(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        Class = kendo.Class,
        Widget = ui.Widget,
        DataSource = kendo.data.DataSource,
        toString = {}.toString,
        identity = function (o) { return o; },
        map = $.map,
        extend = $.extend,
        isFunction = kendo.isFunction,
        CHANGE = "change",
        ERROR = "error",
        PROGRESS = "progress",
        STATERESET = "stateReset",
        AUTO = "auto",
        DIV = "<div/>",
        NS = ".kendoPivotGrid",
        ROW_TOTAL_KEY = "__row_total__",
        DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        EXPANDMEMBER = "expandMember",
        COLLAPSEMEMBER = "collapseMember",
        STATE_EXPANDED = "k-i-arrow-s",
        STATE_COLLAPSED = "k-i-arrow-e",
        HEADER_TEMPLATE = "#: data.member.caption || data.member.name #",
        KPISTATUS_TEMPLATE = '<span class="k-icon k-i-kpi-#=data.dataItem.value > 0 ? \"open\" : data.dataItem.value < 0 ? \"denied\" : \"hold\"#">#:data.dataItem.value#</span>',
        KPITREND_TEMPLATE = '<span class="k-icon k-i-kpi-#=data.dataItem.value > 0 ? \"increase\" : data.dataItem.value < 0 ? \"decrease\" : \"equal\"#">#:data.dataItem.value#</span>',
        DATACELL_TEMPLATE = '#= data.dataItem ? kendo.htmlEncode(data.dataItem.fmtValue || data.dataItem.value) || "&nbsp;" : "&nbsp;" #',
        LAYOUT_TABLE = '<table class="k-pivot-layout">' +
                            '<tr>' +
                                '<td>' +
                                    '<div class="k-pivot-rowheaders"></div>' +
                                '</td>' +
                                '<td>' +
                                    '<div class="k-pivot-table k-state-default"></div>' +
                                '</td>' +
                            '</tr>' +
                        '</table>';

    function normalizeMeasures(measure) {
        var descriptor = typeof measure === "string" ? [{ name: measure}] : measure;
        var descriptors = toString.call(descriptor) === "[object Array]" ? descriptor : (descriptor !== undefined ? [descriptor] : []);

        return map(descriptors, function (d) {
            if (typeof d === "string") {
                return { name: d };
            }
            return { name: d.name, type: d.type };
        });
    }

    function normalizeMembers(member) {
        var descriptor = typeof member === "string" ? [{ name: [member], expand: false}] : member;
        var descriptors = toString.call(descriptor) === "[object Array]" ? descriptor : (descriptor !== undefined ? [descriptor] : []);

        return map(descriptors, function (d) {
            if (typeof d === "string") {
                return { name: [d], expand: false };
            }
            return { name: (toString.call(d.name) === "[object Array]" ? d.name.slice() : [d.name]), expand: d.expand };
        });
    }

    function parentName(tuple, level) {
        var member = tuple.members[level];
        var parentNameValue = buildPath(tuple, level - 1);

        if (member.parentName) {
            parentNameValue.push(member.parentName);
        }

        if (!parentNameValue.length) {
            parentNameValue = "";
        }

        return kendo.stringify(parentNameValue);
    }

    function accumulateMembers(accumulator, rootTuple, tuple, level) {
        var idx, length;
        var children;
        var member;

        if (!tuple) {
            tuple = rootTuple;
        }

        if (!level) {
            level = 0;
        }

        member = tuple.members[level];

        if (!member || member.measure) { //return if no member or measure
            return;
        }

        children = member.children;
        length = children.length;

        if (tuple === rootTuple) {
            accumulator[kendo.stringify([member.name])] = !!length;
        } else if (length) {
            accumulator[kendo.stringify(buildPath(tuple, level))] = true;
        }

        if (length) {
            for (idx = 0; idx < length; idx++) {
                accumulateMembers(accumulator, rootTuple, children[idx], level);
            }
        }

        accumulateMembers(accumulator, rootTuple, tuple, level + 1);
    }

    function descriptorsForAxes(tuples) {
        var result = {};

        if (tuples.length) {
            accumulateMembers(result, tuples[0]);
        }

        var descriptors = [];
        for (var k in result) {
            descriptors.push({ name: $.parseJSON(k), expand: result[k] });
        }

        return descriptors;
    }

    function addMissingPathMembers(members, axis) {
        var tuples = axis.tuples || [];
        var firstTuple = tuples[0];

        if (firstTuple && members.length < firstTuple.members.length) {
            var tupleMembers = firstTuple.members;

            for (var idx = 0; idx < tupleMembers.length; idx++) {
                if (tupleMembers[idx].measure) {
                    continue;
                }

                var found = false;
                for (var j = 0; j < members.length; j++) {
                    if (getName(members[j]).indexOf(tupleMembers[idx].hierarchy) === 0) {
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    members.push({ name: [tupleMembers[idx].name], expand: false }); //calling normalize here to make name from string to array
                }
            }
        }
    }

    function tupleToDescriptors(tuple) {
        var result = [];
        var members = tuple.members;

        for (var idx = 0; idx < members.length; idx++) {
            if (members[idx].measure) {
                continue;
            }

            //make tuple name an array
            result.push({ name: [members[idx].name], expand: members[idx].children.length > 0 });
        }

        return result;
    }

    function descriptorsForMembers(axis, members, measures) {
        axis = axis || {};

        addMissingPathMembers(members, axis);

        if (measures.length > 1) {
            members.push({
                name: "Measures",
                measure: true,
                children: normalizeMembers(measures)
            });
        }

        var tupletoSearch = {
            members: members
        };

        if (axis.tuples) {
            var result = findExistingTuple(axis.tuples, tupletoSearch);
            if (result.tuple) {
                members = tupleToDescriptors(result.tuple);
            }
        }

        return members;
    }

    function addDataCellVertical(result, rowIndex, map, key, resultFuncs, formats, offset) {
        var value, aggregate, columnKey, resultFunc, format, measuresCount = 0;

        var start = rowIndex;

        for (aggregate in map[key].aggregates) {
            value = map[key].aggregates[aggregate];
            resultFunc = resultFuncs[aggregate];
            format = formats[aggregate];

            value = resultFunc ? resultFunc(value) : value.accumulator;

            result[start] = {
                ordinal: start,
                value: value,
                fmtValue: format ? kendo.format(format, value) : value
            };
            ++measuresCount;
            start += offset;
        }

        var items = map[key].items;

        for (columnKey in items) {
            var index = items[columnKey].index * measuresCount;

            index = start + index * offset;

            for (aggregate in items[columnKey].aggregates) {
                value = items[columnKey].aggregates[aggregate];
                resultFunc = resultFuncs[aggregate];
                format = formats[aggregate];

                value = resultFunc ? resultFunc(value) : value.accumulator;

                result[index] = {
                    ordinal: index,
                    value: value,
                    fmtValue: format ? kendo.format(format, value) : value
                };
                index += offset;
            }
        }
    }

    function addDataCell(result, rowIndex, map, key, resultFuncs, formats) {
        var value, aggregate, columnKey, resultFunc, format, measuresCount = 0;

        for (aggregate in map[key].aggregates) {
            value = map[key].aggregates[aggregate];
            resultFunc = resultFuncs[aggregate];
            format = formats[aggregate];

            value = resultFunc ? resultFunc(value) : value.accumulator;

            result[result.length] = {
                ordinal: rowIndex++,
                value: value,
                fmtValue: format ? kendo.format(format, value) : value
            };
            ++measuresCount;
        }

        var items = map[key].items;

        for (columnKey in items) {
            var index = items[columnKey].index * measuresCount;

            for (aggregate in items[columnKey].aggregates) {
                value = items[columnKey].aggregates[aggregate];
                resultFunc = resultFuncs[aggregate];
                format = formats[aggregate];

                value = resultFunc ? resultFunc(value) : value.accumulator;

                result[result.length] = {
                    ordinal: rowIndex + index++,
                    value: value,
                    fmtValue: format ? kendo.format(format, value) : value
                };
            }
        }
    }

    function createAggregateGetter(m) {
        var measureGetter = kendo.getter(m.field, true);
        return function (aggregatorContext, state) {
            return m.aggregate(measureGetter(aggregatorContext.dataItem), state, aggregatorContext);
        };
    }

    function isNumber(val) {
        return typeof val === "number" && !isNaN(val);
    }

    function isDate(val) {
        return val && val.getTime;
    }

    var functions = {
        sum: function (value, state) {
            var accumulator = state.accumulator;

            if (!isNumber(accumulator)) {
                accumulator = value;
            } else if (isNumber(value)) {
                accumulator += value;
            }

            return accumulator;
        },
        count: function (value, state) {
            return (state.accumulator || 0) + 1;
        },
        average: {
            aggregate: function (value, state) {
                var accumulator = state.accumulator;

                if (state.count === undefined) {
                    state.count = 0;
                }

                if (!isNumber(accumulator)) {
                    accumulator = value;
                } else if (isNumber(value)) {
                    accumulator += value;
                }

                if (isNumber(value)) {
                    state.count++;
                }

                return accumulator;
            },
            result: function (state) {
                var accumulator = state.accumulator;

                if (isNumber(accumulator)) {
                    accumulator = accumulator / state.count;
                }

                return accumulator;
            }
        },
        max: function (value, state) {
            var accumulator = state.accumulator;

            if (!isNumber(accumulator) && !isDate(accumulator)) {
                accumulator = value;
            }

            if (accumulator < value && (isNumber(value) || isDate(value))) {
                accumulator = value;
            }
            return accumulator;
        },
        min: function (value, state) {
            var accumulator = state.accumulator;

            if (!isNumber(accumulator) && !isDate(accumulator)) {
                accumulator = value;
            }

            if (accumulator > value && (isNumber(value) || isDate(value))) {
                accumulator = value;
            }
            return accumulator;
        }
    };

    var PivotCubeBuilder = Class.extend({
        init: function (options) {
            this.options = extend({}, this.options, options);
            this.dimensions = this._normalizeDescriptors("field", this.options.dimensions);
            this.measures = this._normalizeDescriptors("name", this.options.measures);
        },

        _normalizeDescriptors: function (keyField, descriptors) {
            descriptors = descriptors || {};
            var fields = {};
            var field;

            if (toString.call(descriptors) === "[object Array]") {
                for (var idx = 0, length = descriptors.length; idx < length; idx++) {
                    field = descriptors[idx];
                    if (typeof field === "string") {
                        fields[field] = {};
                    } else if (field[keyField]) {
                        fields[field[keyField]] = field;
                    }
                }
                descriptors = fields;
            }

            return descriptors;
        },

        _asTuples: function (map, descriptors, measureAggregators) {
            measureAggregators = measureAggregators || [];

            var dimensionsSchema = this.dimensions || [];
            var result = [];
            var root;
            var idx;
            var length;
            var measureIdx;
            var tuple;
            var name;
            var aggregatorsLength = measureAggregators.length || 1;

            if (descriptors.length || measureAggregators.length) {
                for (measureIdx = 0; measureIdx < aggregatorsLength; measureIdx++) {

                    root = { members: [] };

                    for (idx = 0, length = descriptors.length; idx < length; idx++) {
                        name = getName(descriptors[idx].name);

                        root.members[root.members.length] = {
                            children: [],
                            caption: (dimensionsSchema[name] || {}).caption || "All",
                            name: name,
                            levelName: name,
                            levelNum: "0",
                            hasChildren: true,
                            parentName: undefined,
                            hierarchy: name
                        };
                    }

                    if (aggregatorsLength > 1) {
                        root.members[root.members.length] = {
                            children: [],
                            caption: (measureAggregators[measureIdx]).caption,
                            name: measureAggregators[measureIdx].descriptor.name,
                            levelName: "MEASURES",
                            levelNum: "0",
                            hasChildren: false,
                            parentName: undefined,
                            hierarchy: "MEASURES"
                        };
                    }
                    result[result.length] = root;
                }
            }

            for (var key in map) {
                for (measureIdx = 0; measureIdx < aggregatorsLength; measureIdx++) {
                    tuple = { members: [] };
                    for (idx = 0, length = descriptors.length; idx < length; idx++) {
                        name = getName(descriptors[idx].name);

                        if (map[key].parentName.indexOf(name) === 0) {
                            tuple.members[tuple.members.length] = {
                                children: [],
                                caption: map[key].value,
                                name: map[key].name,
                                levelName: map[key].name,
                                levelNum: 1,
                                hasChildren: false,
                                parentName: name,
                                hierarchy: name
                            };
                        } else {
                            tuple.members[tuple.members.length] = {
                                children: [],
                                caption: (dimensionsSchema[name] || {}).caption || "All",
                                name: name,
                                levelName: name,
                                levelNum: "0",
                                hasChildren: true,
                                parentName: undefined,
                                hierarchy: name
                            };
                        }
                    }

                    if (aggregatorsLength > 1) {
                        tuple.members[tuple.members.length] = {
                            children: [],
                            caption: measureAggregators[measureIdx].caption,
                            name: measureAggregators[measureIdx].descriptor.name,
                            levelName: "MEASURES",
                            levelNum: "0",
                            hasChildren: true,
                            parentName: undefined,
                            hierarchy: "MEASURES"
                        };
                    }

                    result[result.length] = tuple;
                }
            }

            return result;
        },

        _toDataArray: function (map, rowStartOffset, measures, offset, addFunc) {
            var formats = {};
            var resultFuncs = {};
            var descriptors, measure, name;

            var idx = 0;
            var length = measures && measures.length;

            if (length) {
                descriptors = (this.measures || {});
                for (; idx < length; idx++) {
                    name = measures[idx].name;
                    measure = descriptors[name];

                    if (measure.result) {
                        resultFuncs[name] = measure.result;
                    }

                    if (measure.format) {
                        formats[name] = measure.format;
                    }
                }
            }

            var result = [];
            var items;
            var rowIndex = 0;

            addFunc(result, rowIndex, map, ROW_TOTAL_KEY, resultFuncs, formats, rowStartOffset);

            for (var key in map) {
                if (key === ROW_TOTAL_KEY) {
                    continue;
                }

                rowIndex += offset;
                addFunc(result, rowIndex, map, key, resultFuncs, formats, rowStartOffset);
            }

            return result;
        },

        _matchDescriptors: function (dataItem, descriptors, getters, idx) {
            var descriptor;
            var parts;
            var parentField;
            var expectedValue;
            var parentGetter;

            while (idx > 0) {
                descriptor = descriptors[--idx];
                parts = getName(descriptor).split("&");
                if (parts.length > 1) {
                    parentField = parts[0];
                    expectedValue = parts[1];
                    parentGetter = getters[parentField];

                    if (parentGetter(dataItem) != expectedValue) {
                        return false;
                    }
                }
            }
            return true;
        },

        _isExpanded: function (descriptors) {
            for (var idx = 0, length = descriptors.length; idx < length; idx++) {
                if (descriptors[idx].expand) {
                    return true;
                }
            }
            return false;
        },

        _calculateAggregate: function (measureAggregators, aggregatorContext, totalItem) {
            var result = {};
            var state;
            var name;

            for (var measureIdx = 0; measureIdx < measureAggregators.length; measureIdx++) {
                name = measureAggregators[measureIdx].descriptor.name;
                state = totalItem.aggregates[name] || {};
                state.accumulator = measureAggregators[measureIdx].aggregator(aggregatorContext, state);
                result[name] = state;
            }

            return result;
        },

        _processColumns: function (measureAggregators, descriptors, getters, columns, aggregatorContext, rowTotal, state, updateColumn) {
            var value;
            var descriptor;
            var name;
            var column;
            var totalItem;
            var dataItem = aggregatorContext.dataItem;

            for (var idx = 0; idx < descriptors.length; idx++) {
                descriptor = descriptors[idx];

                if (descriptor.expand) {
                    if (!this._matchDescriptors(dataItem, descriptors, getters, idx)) {
                        continue;
                    }

                    name = getName(descriptor);

                    value = getters[name](dataItem);
                    value = value !== undefined ? value.toString() : value;

                    name = name + "&" + value;

                    column = columns[name] || {
                        index: state.columnIndex,
                        name: name,
                        parentName: name,
                        value: value
                    };

                    totalItem = rowTotal.items[name] || {
                        aggregates: {}
                    };

                    rowTotal.items[name] = {
                        index: column.index,
                        aggregates: this._calculateAggregate(measureAggregators, aggregatorContext, totalItem)
                    };

                    if (updateColumn) {
                        if (!columns[name]) {
                            state.columnIndex++;
                        }
                        columns[name] = column;
                    }
                }
            }
        },

        _measureAggregators: function (options) {
            var measureDescriptors = options.measures || [];
            var measures = this.measures || {};
            var aggregators = [];
            var descriptor, measure, idx, length;
            var defaultAggregate, aggregate;

            if (measureDescriptors.length) {
                for (idx = 0, length = measureDescriptors.length; idx < length; idx++) {
                    descriptor = measureDescriptors[idx];
                    measure = measures[descriptor.name];
                    defaultAggregate = null;

                    if (measure) {
                        aggregate = measure.aggregate;
                        if (typeof aggregate === "string") {
                            defaultAggregate = functions[aggregate.toLowerCase()];

                            if (!defaultAggregate) {
                                throw new Error("There is no such aggregate function");
                            }

                            measure.aggregate = defaultAggregate.aggregate || defaultAggregate;
                            measure.result = defaultAggregate.result;
                        }


                        aggregators.push({
                            descriptor: descriptor,
                            caption: measure.caption,
                            result: measure.result,
                            aggregator: createAggregateGetter(measure)
                        });
                    }
                }
            } else {
                aggregators.push({
                    descriptor: { name: "default" },
                    caption: "default",
                    aggregator: function () { return 1; }
                });
            }

            return aggregators;
        },

        _buildGetters: function (descriptors) {
            var result = {};
            var descriptor;
            var parts;
            var name;

            for (var idx = 0, length = descriptors.length; idx < length; idx++) {
                descriptor = descriptors[idx];

                name = getName(descriptor);

                parts = name.split("&");

                if (parts.length > 1) {
                    result[parts[0]] = kendo.getter(parts[0], true);
                } else {
                    result[name] = kendo.getter(name, true);
                }
            }

            return result;
        },

        process: function (data, options) {
            data = data || [];
            options = options || {};

            var measures = options.measures || [];

            var measuresRowAxis = options.measuresAxis === "rows";

            var columnDescriptors = (measuresRowAxis ? options.rows : options.columns) || [];
            var rowDescriptors = (!measuresRowAxis ? options.rows : options.columns) || [];

            if (!columnDescriptors.length && rowDescriptors.length && (!measures.length || (measures.length && measuresRowAxis))) {
                columnDescriptors = rowDescriptors;
                rowDescriptors = [];
                measuresRowAxis = false;
            }

            if (!columnDescriptors.length && !rowDescriptors.length) {
                measuresRowAxis = false;
            }

            if (!columnDescriptors.length && measures.length) {
                columnDescriptors = normalizeMembers(options.measures);
            }

            var aggregatedData = {};
            var columns = {};
            var rows = {};

            var rowValue;
            var state = { columnIndex: 0 };

            var measureAggregators = this._measureAggregators(options);
            var columnGetters = this._buildGetters(columnDescriptors);
            var rowGetters = this._buildGetters(rowDescriptors);

            var processed = false;

            if (columnDescriptors.length || rowDescriptors.length) {
                var dataItem;
                var aggregatorContext;
                var hasExpandedRows = this._isExpanded(rowDescriptors);

                processed = true;

                for (var idx = 0, length = data.length; idx < length; idx++) {
                    dataItem = data[idx];

                    aggregatorContext = {
                        dataItem: dataItem,
                        index: idx
                    };

                    var rowTotal = aggregatedData[ROW_TOTAL_KEY] || {
                        items: {},
                        aggregates: {}
                    };

                    this._processColumns(measureAggregators, columnDescriptors, columnGetters, columns, aggregatorContext, rowTotal, state, !hasExpandedRows);

                    rowTotal.aggregates = this._calculateAggregate(measureAggregators, aggregatorContext, rowTotal);
                    aggregatedData[ROW_TOTAL_KEY] = rowTotal;

                    for (var rowIdx = 0, rowLength = rowDescriptors.length; rowIdx < rowLength; rowIdx++) {
                        var rowDescriptor = rowDescriptors[rowIdx];

                        if (rowDescriptor.expand) {
                            if (!this._matchDescriptors(dataItem, rowDescriptors, rowGetters, rowIdx)) {
                                continue;
                            }

                            var rowName = getName(rowDescriptor);

                            rowValue = rowGetters[rowName](dataItem);
                            rowValue = rowValue !== undefined ? rowValue.toString() : rowValue;
                            rows[rowValue] = {
                                name: rowName + "&" + rowValue,
                                parentName: rowName,
                                value: rowValue
                            };

                            var value = aggregatedData[rowValue] || {
                                items: {},
                                aggregates: {}
                            };

                            this._processColumns(measureAggregators, columnDescriptors, columnGetters, columns, aggregatorContext, value, state, true);

                            value.aggregates = this._calculateAggregate(measureAggregators, aggregatorContext, value);
                            aggregatedData[rowValue] = value;
                        }
                    }
                }
            }

            if (processed && data.length) {
                if (measureAggregators.length > 1 && (!options.columns || !options.columns.length)) {
                    columnDescriptors = [];
                }

                columns = this._asTuples(columns, columnDescriptors, measureAggregators);
                rows = this._asTuples(rows, rowDescriptors, []);

                var offset = columns.length;

                if (measuresRowAxis) {
                    offset = 1;

                    var tmp = columns;
                    columns = rows;
                    rows = tmp;
                }

                aggregatedData = this._toDataArray(aggregatedData, columns.length, options.measures, offset, measuresRowAxis ? addDataCellVertical : addDataCell);
                aggregatedData = this._normalizeData(aggregatedData, columns.length, rows.length);
            } else {
                aggregatedData = columns = rows = [];
            }

            return {
                axes: {
                    columns: { tuples: columns },
                    rows: { tuples: rows }
                },
                data: aggregatedData
            };
        },

        _normalizeData: function (data, columns, rows) {
            var axesLength = (columns || 1) * (rows || 1);
            var result = new Array(axesLength);
            var length = data.length;
            var cell, idx;

            if (length === axesLength) {
                return data;
            }

            for (idx = 0; idx < axesLength; idx++) {
                result[idx] = { value: "", fmtValue: "", ordinal: idx };
            }

            for (idx = 0; idx < length; idx++) {
                cell = data[idx];
                if (cell) {
                    result[cell.ordinal] = cell;
                }
            }

            return result;
        }
    });

    var PivotTransport = Class.extend({
        init: function (options, transport) {
            this.transport = transport;
            this.options = transport.options || {};

            if (!this.transport.discover) {
                if (isFunction(options.discover)) {
                    this.discover = options.discover;
                }
            }
        },
        read: function (options) {
            return this.transport.read(options);
        },
        update: function (options) {
            return this.transport.update(options);
        },
        create: function (options) {
            return this.transport.create(options);
        },
        destroy: function (options) {
            return this.transport.destroy(options);
        },
        discover: function (options) {
            if (this.transport.discover) {
                return this.transport.discover(options);
            }
            options.success({});
        },
        catalog: function (val) {
            var options = this.options || {};

            if (val === undefined) {
                return (options.connection || {}).catalog;

            }

            var connection = options.connection || {};
            connection.catalog = val;

            this.options.connection = connection;
            $.extend(this.transport.options, { connection: connection });
        },
        cube: function (val) {
            var options = this.options || {};

            if (val === undefined) {
                return (options.connection || {}).cube;
            }

            var connection = options.connection || {};
            connection.cube = val;

            this.options.connection = connection;
            extend(true, this.transport.options, { connection: connection });
        }
    });

    var PivotDataSource = DataSource.extend({
        init: function (options) {
            var cube = ((options || {}).schema || {}).cube;
            var measuresAxis = "columns";
            var measures;

            var schema = {
                axes: identity,
                cubes: identity,
                catalogs: identity,
                measures: identity,
                dimensions: identity,
                hierarchies: identity,
                levels: identity,
                members: identity
            };

            if (cube) {
                schema = $.extend(schema, this._cubeSchema(cube));
                this.cubeBuilder = new PivotCubeBuilder(cube);
            }

            DataSource.fn.init.call(this, extend(true, {}, {
                schema: schema
            }, options));

            this.transport = new PivotTransport(this.options.transport || {}, this.transport);

            this._columns = normalizeMembers(this.options.columns);
            this._rows = normalizeMembers(this.options.rows);

            measures = this.options.measures || [];

            if (this.options.measures !== null && toString.call(this.options.measures) === "[object Object]") {
                measures = this.options.measures.values || [];
                measuresAxis = this.options.measures.axis || "columns";
            }

            this._measures = normalizeMeasures(measures || []);
            this._measuresAxis = measuresAxis;

            this._axes = {};
        },

        _cubeSchema: function (cube) {
            return {
                dimensions: function () {
                    var result = [];
                    var dimensions = cube.dimensions;

                    for (var key in dimensions) {
                        result.push({
                            name: key,
                            caption: dimensions[key].caption || key,
                            uniqueName: key,
                            defaultHierarchy: key,
                            type: 1
                        });
                    }

                    if (cube.measures) {
                        result.push({
                            name: "Measures",
                            caption: "Measures",
                            uniqueName: "Measures",
                            type: 2
                        });
                    }

                    return result;
                },
                hierarchies: function () {
                    return [];
                },
                measures: function () {
                    var result = [];
                    var measures = cube.measures;

                    for (var key in measures) {
                        result.push({
                            name: key,
                            caption: key,
                            uniqueName: key,
                            aggregator: key
                        });
                    }

                    return result;
                }
            };
        },

        options: {
            serverSorting: true,
            serverPaging: true,
            serverFiltering: true,
            serverGrouping: true,
            serverAggregates: true
        },

        catalog: function (val) {
            if (val === undefined) {
                return this.transport.catalog();
            }

            this.transport.catalog(val);
            this._mergeState({}); // clears current state
            this._axes = {};
            this.data([]);
        },

        cube: function (val) {
            if (val === undefined) {
                return this.transport.cube();
            }

            this.transport.cube(val);
            this._axes = {};
            this._mergeState({}); // clears current state
            this.data([]);
        },

        axes: function () {
            return this._axes;
        },

        columns: function (val) {
            if (val === undefined) {
                return this._columns;
            }

            this._clearAxesData = true;
            this._columns = normalizeMembers(val);
            this.query({
                columns: val,
                rows: this.rowsAxisDescriptors(),
                measures: this.measures()
            });
        },

        rows: function (val) {
            if (val === undefined) {
                return this._rows;
            }

            this._clearAxesData = true;
            this._rows = normalizeMembers(val);

            this.query({
                columns: this.columnsAxisDescriptors(),
                rows: val,
                measures: this.measures()
            });
        },

        measures: function (val) {
            if (val === undefined) {
                return this._measures;
            }

            this._clearAxesData = true;
            this.query({
                columns: this.columnsAxisDescriptors(),
                rows: this.rowsAxisDescriptors(),
                measures: normalizeMeasures(val)
            });
        },

        measuresAxis: function () {
            return this._measuresAxis || "columns";
        },

        _expandPath: function (path, axis) {
            var origin = axis === "columns" ? "columns" : "rows";
            var other = axis === "columns" ? "rows" : "columns";

            var members = normalizeMembers(path);
            var memberToExpand = getName(members[members.length - 1]);

            this._lastExpanded = origin;

            members = descriptorsForMembers(this.axes()[origin], members, this.measures());

            for (var idx = 0; idx < members.length; idx++) {
                var memberName = getName(members[idx]);

                if (memberName === memberToExpand) {
                    if (members[idx].expand) {
                        return;
                    }
                    members[idx].expand = true;
                } else {
                    members[idx].expand = false;
                }
            }

            var descriptors = {};
            descriptors[origin] = members;
            descriptors[other] = this._descriptorsForAxis(other);

            this._query(descriptors);
        },

        _descriptorsForAxis: function (axis) {
            var axes = this.axes();
            var descriptors = this[axis]() || [];

            if (axes && axes[axis] && axes[axis].tuples && axes[axis].tuples[0]) {
                descriptors = descriptorsForAxes(axes[axis].tuples || []);
            }
            return descriptors;
        },

        columnsAxisDescriptors: function () {
            return this._descriptorsForAxis("columns");
        },

        rowsAxisDescriptors: function () {
            return this._descriptorsForAxis("rows");
        },

        _process: function (data, e) {
            this._view = data;

            e = e || {};
            e.items = e.items || this._view;

            this.trigger(CHANGE, e);
        },

        _query: function (options) {
            var that = this;

            if (!options) {
                this._clearAxesData = true;
            }

            return that.query(extend({}, {
                page: that.page(),
                pageSize: that.pageSize(),
                sort: that.sort(),
                filter: that.filter(),
                group: that.group(),
                aggregate: that.aggregate(),
                columns: this.columnsAxisDescriptors(),
                rows: this.rowsAxisDescriptors(),
                measures: this.measures()
            }, options));
        },

        query: function (options) {
            var state = this._mergeState(options);

            if (this._data.length && this.cubeBuilder) {
                this._params(state);
                this._updateLocalData(this._pristineData);

                return $.Deferred().resolve().promise();
            }

            return this.read(state);
        },

        _mergeState: function (options) {
            options = DataSource.fn._mergeState.call(this, options);

            if (options !== undefined) {
                this._measures = normalizeMeasures(options.measures);

                if (options.columns) {
                    options.columns = normalizeMembers(options.columns);
                } else if (!options.columns) {
                    this._columns = [];
                }

                if (options.rows) {
                    options.rows = normalizeMembers(options.rows);
                } else if (!options.rows) {
                    this._rows = [];
                }
            }
            return options;
        },

        filter: function (val) {
            if (val === undefined) {
                return this._filter;
            }

            this._clearAxesData = true;
            this._query({ filter: val, page: 1 });
        },

        expandColumn: function (path) {
            this._expandPath(path, "columns");
        },

        expandRow: function (path) {
            this._expandPath(path, "rows");
        },

        success: function (data) {
            var originalData;
            if (this.cubeBuilder) {
                originalData = (this.reader.data(data) || []).slice(0);
            }
            DataSource.fn.success.call(this, data);
            if (originalData) {
                this._pristineData = originalData;
            }
        },

        _processResult: function (data, axes) {
            if (this.cubeBuilder) {
                var processedData = this.cubeBuilder.process(data, this._requestData);

                data = processedData.data;
                axes = processedData.axes;
            }

            var columnIndexes, rowIndexes;
            var tuples, resultAxis, measures, axisToSkip;
            var columnDescriptors = this.columns().length;
            var rowDescriptors = this.rows().length;
            var hasColumnTuples = axes.columns && axes.columns.tuples;

            if (!columnDescriptors && rowDescriptors && hasColumnTuples && (this._rowMeasures().length || !this.measures().length)) {
                axes = {
                    columns: {},
                    rows: axes.columns
                };
            }

            if (!columnDescriptors && !rowDescriptors && this.measuresAxis() === "rows" && hasColumnTuples) {
                axes = {
                    columns: {},
                    rows: axes.columns
                };
            }

            this._axes = {
                columns: normalizeAxis(this._axes.columns),
                rows: normalizeAxis(this._axes.rows)
            };

            axes = {
                columns: normalizeAxis(axes.columns),
                rows: normalizeAxis(axes.rows)
            };

            columnIndexes = this._normalizeTuples(axes.columns.tuples, this._axes.columns.tuples, this._columnMeasures());
            rowIndexes = this._normalizeTuples(axes.rows.tuples, this._axes.rows.tuples, this._rowMeasures());

            if (!this.cubeBuilder) {
                data = this._normalizeData({
                    columnsLength: axes.columns.tuples.length,
                    rowsLength: axes.rows.tuples.length,
                    columnIndexes: columnIndexes,
                    rowIndexes: rowIndexes,
                    data: data
                });
            }

            if (this._lastExpanded == "rows") {
                tuples = axes.columns.tuples;
                measures = this._columnMeasures();
                resultAxis = validateAxis(axes.columns, this._axes.columns, measures);

                if (resultAxis) {
                    axisToSkip = "columns";
                    axes.columns = resultAxis;
                    adjustDataByColumn(tuples, resultAxis.tuples, axes.rows.tuples.length, measures, data);
                    data = this._normalizeData({
                        columnsLength: membersCount(axes.columns.tuples, measures),
                        rowsLength: axes.rows.tuples.length,
                        data: data
                    });
                }
            } else if (this._lastExpanded == "columns") {
                tuples = axes.rows.tuples;
                measures = this._rowMeasures();
                resultAxis = validateAxis(axes.rows, this._axes.rows, measures);

                if (resultAxis) {
                    axisToSkip = "rows";
                    axes.rows = resultAxis;
                    adjustDataByRow(tuples, resultAxis.tuples, axes.columns.tuples.length, measures, data);

                    data = this._normalizeData({
                        columnsLength: membersCount(axes.rows.tuples, measures),
                        rowsLength: axes.columns.tuples.length,
                        data: data
                    });
                }
            }

            this._lastExpanded = null;

            var result = this._mergeAxes(axes, data, axisToSkip);
            this._axes = result.axes;

            return result.data;
        },

        _readData: function (data) {
            var axes = this.reader.axes(data);
            var newData = this.reader.data(data);

            return this._processResult(newData, axes);
        },

        _createTuple: function (tuple, measure, buildRoot) {
            var name;
            var member;
            var parentName;
            var members = tuple.members;
            var length = members.length;
            var root = { members: [] };
            var levelNum;
            var caption;
            var idx = 0;

            if (measure) {
                length -= 1;
            }

            for (; idx < length; idx++) {
                member = members[idx];
                levelNum = Number(member.levelNum);

                name = member.name;
                parentName = member.parentName;
                caption = member.caption || name;

                if (buildRoot) {
                    caption = "All";
                    if (levelNum === 0) {
                        parentName = member.name;
                    } else {
                        levelNum -= 1;
                    }

                    name = parentName;
                }

                root.members.push({
                    name: name,
                    children: [],
                    caption: caption,
                    levelName: parentName,
                    levelNum: levelNum.toString(),
                    hasChildren: buildRoot,
                    hierarchy: parentName,
                    parentName: !buildRoot ? parentName : ""
                });
            }

            if (measure) {
                root.members.push({
                    name: measure.name,
                    children: []
                });
            }

            return root;
        },

        _hasRoot: function (target, source) {
            if (source.length) {
                return findExistingTuple(source, target).tuple;
            }

            var members = target.members;
            var member;

            var isRoot = true;
            var levelNum;

            for (var idx = 0, length = members.length; idx < length; idx++) {
                member = members[idx];
                levelNum = Number(member.levelNum) || 0;

                if (levelNum !== 0) {
                    isRoot = false;
                    break;
                }
            }

            return isRoot;
        },

        _mergeAxes: function (sourceAxes, data, axisToSkip) {
            var columnMeasures = this._columnMeasures();
            var rowMeasures = this._rowMeasures();
            var axes = this.axes();
            var startIndex, tuples;

            var newRowsLength = sourceAxes.rows.tuples.length;
            var oldColumnsLength = membersCount(axes.columns.tuples, columnMeasures);
            var newColumnsLength = sourceAxes.columns.tuples.length;

            if (axisToSkip == "columns") {
                newColumnsLength = oldColumnsLength;
                tuples = sourceAxes.columns.tuples;
            } else {
                tuples = parseSource(sourceAxes.columns.tuples, columnMeasures);
                data = prepareDataOnColumns(tuples, data);
            }

            var mergedColumns = mergeTuples(axes.columns.tuples, tuples, columnMeasures);

            if (axisToSkip == "rows") {
                newRowsLength = membersCount(sourceAxes.rows.tuples, rowMeasures);
                tuples = sourceAxes.rows.tuples;
            } else {
                tuples = parseSource(sourceAxes.rows.tuples, rowMeasures);
                data = prepareDataOnRows(tuples, data);
            }
            var mergedRows = mergeTuples(axes.rows.tuples, tuples, rowMeasures);

            axes.columns.tuples = mergedColumns.tuples;
            axes.rows.tuples = mergedRows.tuples;

            if (oldColumnsLength !== membersCount(axes.columns.tuples, columnMeasures)) {
                //columns are expanded
                startIndex = mergedColumns.index + findDataIndex(mergedColumns.parsedRoot, mergedColumns.memberIndex, columnMeasures);
                var offset = oldColumnsLength + newColumnsLength;
                data = this._mergeColumnData(data, startIndex, newRowsLength, newColumnsLength, offset);
            } else {
                //rows are expanded
                startIndex = mergedRows.index + findDataIndex(mergedRows.parsedRoot, mergedRows.memberIndex, rowMeasures);
                data = this._mergeRowData(data, startIndex, newRowsLength, newColumnsLength);
            }

            return {
                axes: axes,
                data: data
            };
        },

        _mergeColumnData: function (newData, columnIndex, rowsLength, columnsLength, offset) {
            var data = this.data().toJSON();
            var rowIndex, index, drop = 0, toAdd;
            var columnMeasures = Math.max(this._columnMeasures().length, 1);

            rowsLength = Math.max(rowsLength, 1);

            if (data.length > 0) {
                //if there is already data, drop the first new data item
                drop = columnMeasures;
                offset -= columnMeasures;
            }

            for (rowIndex = 0; rowIndex < rowsLength; rowIndex++) {
                index = columnIndex + (rowIndex * offset);
                toAdd = newData.splice(0, columnsLength);
                toAdd.splice(0, drop);
                [ ].splice.apply(data, [index, 0].concat(toAdd));
            }

            return data;
        },

        _mergeRowData: function (newData, rowIndex, rowsLength, columnsLength) {
            var data = this.data().toJSON();
            var idx, dataIndex, toAdd;
            var rowMeasures = Math.max(this._rowMeasures().length, 1);

            columnsLength = Math.max(columnsLength, 1);
            if (data.length > 0) {
                //if there is already data, drop the first new data item
                rowsLength -= rowMeasures;
                newData.splice(0, columnsLength * rowMeasures);
            }

            for (idx = 0; idx < rowsLength; idx++) {
                toAdd = newData.splice(0, columnsLength);
                dataIndex = (rowIndex * columnsLength) + (idx * columnsLength);
                [ ].splice.apply(data, [dataIndex, 0].concat(toAdd));
            }

            return data;
        },

        _columnMeasures: function () {
            var measures = this.measures();
            var columnMeasures = [];

            if (this.measuresAxis() === "columns") {
                if (this.columns().length === 0) {
                    columnMeasures = measures;
                } else if (measures.length > 1) {
                    columnMeasures = measures;
                }
            }

            return columnMeasures;
        },

        _rowMeasures: function () {
            var measures = this.measures();
            var rowMeasures = [];

            if (this.measuresAxis() === "rows") {
                if (this.rows().length === 0) {
                    rowMeasures = measures;
                } else if (measures.length > 1) {
                    rowMeasures = measures;
                }
            }

            return rowMeasures;
        },

        _updateLocalData: function (data, state) {
            if (this.cubeBuilder) {
                if (state) {
                    this._requestData = state;
                }
                data = this._processResult(data);
            }

            this._data = this._observe(data);

            this._ranges = [];
            this._addRange(this._data);

            this._total = this._data.length;
            this._pristineTotal = this._total;
            this._process(this._data);
        },

        data: function (value) {
            var that = this;
            if (value !== undefined) {
                this._pristineData = value.slice(0);
                this._updateLocalData(value, {
                    columns: this.columns(),
                    rows: this.rows(),
                    measures: this.measures()
                });
            } else {
                return that._data;
            }
        },

        _normalizeTuples: function (tuples, source, measures) {
            var length = measures.length || 1;
            var idx = 0;

            var roots = [];
            var indexes = {};
            var measureIdx = 0;
            var tuple, memberIdx, last;

            if (!tuples.length) {
                return;
            }

            if (!this._hasRoot(tuples[0], source)) {
                for (; idx < length; idx++) {
                    roots.push(this._createTuple(tuples[0], measures[idx], true));
                    indexes[idx] = idx;
                }

                tuples.splice.apply(tuples, [0, tuples.length].concat(roots).concat(tuples));
                idx = length;
            }

            if (measures.length) {
                last = tuple = tuples[idx];
                memberIdx = tuple.members.length - 1;

                while (tuple) {
                    if (measureIdx >= length) {
                        measureIdx = 0;
                    }

                    if (tuple.members[memberIdx].name !== measures[measureIdx].name) {
                        tuples.splice(idx, 0, this._createTuple(tuple, measures[measureIdx]));
                        indexes[idx] = idx;
                    }

                    idx += 1;
                    measureIdx += 1;
                    tuple = tuples[idx];

                    if (length > measureIdx && (!tuple || tupleName(last, memberIdx - 1) !== tupleName(tuple, memberIdx - 1))) {
                        for (; measureIdx < length; measureIdx++) {
                            tuples.splice(idx, 0, this._createTuple(last, measures[measureIdx]));
                            indexes[idx] = idx;
                            idx += 1;
                        }
                        tuple = tuples[idx];
                    }
                    last = tuple;
                }
            }

            return indexes;
        },

        _normalizeData: function (options) {
            var data = options.data;

            var columnIndexes = options.columnIndexes || {};
            var rowIndexes = options.rowIndexes || {};

            var columnsLength = options.columnsLength || 1;
            var length = columnsLength * (options.rowsLength || 1);

            var idx = 0;
            var dataIdx = 0;
            var lastOrdinal = 0;
            var dataItem, i;
            var result = new Array(length);

            if (data.length === length) {
                return data;
            }

            for (; idx < length; idx++) {
                if (rowIndexes[parseInt(idx / columnsLength, 10)] !== undefined) {
                    for (i = 0; i < columnsLength; i++) {
                        result[idx] = { value: "", fmtValue: "", ordinal: idx };
                        idx += 1;
                    }
                }

                if (columnIndexes[idx % columnsLength] !== undefined) {
                    result[idx] = { value: "", fmtValue: "", ordinal: idx };
                    idx += 1;
                }

                dataItem = data[dataIdx];

                if (dataItem) {
                    if (dataItem.ordinal - lastOrdinal > 1) {
                        lastOrdinal += 1;
                        for (; lastOrdinal < dataItem.ordinal; lastOrdinal++) {
                            result[idx] = { value: "", fmtValue: "", ordinal: idx };
                            idx += 1;
                        }
                    }

                    lastOrdinal = dataItem.ordinal;
                    dataItem.ordinal = idx;
                    result[idx] = dataItem;
                    dataIdx += 1;
                } else {
                    result[idx] = { value: "", fmtValue: "", ordinal: idx };
                }
            }

            return result;
        },

        discover: function (options, converter) {
            var that = this,
                transport = that.transport;

            return $.Deferred(function (deferred) {
                transport.discover(extend({
                    success: function (response) {
                        response = that.reader.parse(response);

                        if (that._handleCustomErrors(response)) {
                            return;
                        }

                        if (converter) {
                            response = converter(response);
                        }
                        deferred.resolve(response);
                    },
                    error: function (response, status, error) {
                        deferred.reject(response);
                        that.error(response, status, error);
                    }
                }, options));
            }).promise().done(function () {
                that.trigger("schemaChange");
            });
        },

        schemaMeasures: function () {
            var that = this;

            return that.discover({
                data: {
                    command: "schemaMeasures",
                    restrictions: {
                        catalogName: that.transport.catalog(),
                        cubeName: that.transport.cube()
                    }
                }
            }, function (response) {
                return that.reader.measures(response);
            });
        },

        schemaKPIs: function () {
            var that = this;

            return that.discover({
                data: {
                    command: "schemaKPIs",
                    restrictions: {
                        catalogName: that.transport.catalog(),
                        cubeName: that.transport.cube()
                    }
                }
            }, function (response) {
                return that.reader.kpis(response);
            });
        },

        schemaDimensions: function () {
            var that = this;

            return that.discover({
                data: {
                    command: "schemaDimensions",
                    restrictions: {
                        catalogName: that.transport.catalog(),
                        cubeName: that.transport.cube()
                    }
                }
            }, function (response) {
                return that.reader.dimensions(response);
            });
        },

        schemaHierarchies: function (dimensionName) {
            var that = this;

            return that.discover({
                data: {
                    command: "schemaHierarchies",
                    restrictions: {
                        catalogName: that.transport.catalog(),
                        cubeName: that.transport.cube(),
                        dimensionUniqueName: dimensionName
                    }
                }
            }, function (response) {
                return that.reader.hierarchies(response);
            });
        },

        schemaLevels: function (hierarchyName) {
            var that = this;

            return that.discover({
                data: {
                    command: "schemaLevels",
                    restrictions: {
                        catalogName: that.transport.catalog(),
                        cubeName: that.transport.cube(),
                        hierarchyUniqueName: hierarchyName
                    }
                }
            }, function (response) {
                return that.reader.levels(response);
            });
        },

        schemaCubes: function () {
            var that = this;

            return that.discover({
                data: {
                    command: "schemaCubes",
                    restrictions: {
                        catalogName: that.transport.catalog()
                    }
                }
            }, function (response) {
                return that.reader.cubes(response);
            });
        },

        schemaCatalogs: function () {
            var that = this;

            return that.discover({
                data: {
                    command: "schemaCatalogs"
                }
            }, function (response) {
                return that.reader.catalogs(response);
            });
        },

        schemaMembers: function (restrictions) {
            var that = this;

            return that.discover({
                data: {
                    command: "schemaMembers",
                    restrictions: extend({
                        catalogName: that.transport.catalog(),
                        cubeName: that.transport.cube()
                    }, restrictions)
                }
            }, function (response) {
                return that.reader.members(response);
            });
        },

        _params: function (data) {
            if (this._clearAxesData) {
                this._axes = {};
                this._data = this._observe([]);
                this._clearAxesData = false;
                this.trigger(STATERESET);
            }

            var options = DataSource.fn._params.call(this, data);

            options = extend({
                measures: this.measures(),
                measuresAxis: this.measuresAxis(),
                columns: this.columns(),
                rows: this.rows()
            }, options);

            if (this.cubeBuilder) {
                this._requestData = options;
            }

            return options;
        }
    });

    function validateAxis(newAxis, axis, measures) {
        if (newAxis.tuples.length < membersCount(axis.tuples, measures)) {

            return axis;
        }

        return;
    }

    function adjustDataByColumn(sourceTuples, targetTuples, rowsLength, measures, data) {
        var columnIdx, rowIdx, dataIdx;
        var columnsLength = sourceTuples.length;
        var targetColumnsLength = membersCount(targetTuples, measures);
        var measuresLength = measures.length || 1;

        for (rowIdx = 0; rowIdx < rowsLength; rowIdx++) {
            for (columnIdx = 0; columnIdx < columnsLength; columnIdx++) {
                dataIdx = tupleIndex(sourceTuples[columnIdx], targetTuples) * measuresLength;
                dataIdx += columnIdx % measuresLength;

                data[rowIdx * columnsLength + columnIdx].ordinal = rowIdx * targetColumnsLength + dataIdx;
            }
        }
    }

    function adjustDataByRow(sourceTuples, targetTuples, columnsLength, measures, data) {
        var columnIdx, rowIdx, dataIdx;
        var rowsLength = sourceTuples.length;
        var targetRowsLength = membersCount(targetTuples, measures);
        var measuresLength = measures.length || 1;

        for (rowIdx = 0; rowIdx < rowsLength; rowIdx++) {
            dataIdx = tupleIndex(sourceTuples[rowIdx], targetTuples);
            dataIdx *= measuresLength;
            dataIdx += rowIdx % measuresLength;

            for (columnIdx = 0; columnIdx < columnsLength; columnIdx++) {
                data[rowIdx * columnsLength + columnIdx].ordinal = dataIdx * columnsLength + columnIdx;
            }
        }
    }

    function tupleIndex(tuple, collection) {
        return findExistingTuple(collection, tuple).index;
    }

    function membersCount(tuples, measures) {
        if (!tuples.length) {
            return 0;
        }

        var queue = tuples.slice();
        var current = queue.shift();
        var idx, length, result = 1;

        while (current) {
            if (current.members) {
                [ ].push.apply(queue, current.members);
            } else if (current.children) {
                if (!current.measure) {
                    result += current.children.length;
                }
                [ ].push.apply(queue, current.children);
            }

            current = queue.shift();
        }

        if (measures.length) {
            result = result * measures.length;
        }

        return result;
    }

    function normalizeAxis(axis) {
        if (!axis) {
            axis = {
                tuples: []
            };
        }

        if (!axis.tuples) {
            axis.tuples = [];
        }

        return axis;
    }

    function findDataIndex(tuple, memberIndex, measures) {
        if (!tuple) {
            return 0;
        }

        var counter = Math.max(measures.length, 1);
        var tuples = tuple.members.slice(0, memberIndex);
        var current = tuples.shift();

        while (current) {
            if (current.children) {
                //is member
                [ ].push.apply(tuples, current.children);
            } else {
                //is tuple
                counter++;
                [ ].push.apply(tuples, current.members);
            }

            current = tuples.shift();
        }

        return counter;
    }

    function mergeTuples(target, source, measures) {
        if (!source[0]) {
            return {
                parsedRoot: null,
                tuples: target,
                memberIndex: 0,
                index: 0
            };
        }

        var result = findExistingTuple(target, source[0]);

        if (!result.tuple) {
            return {
                parsedRoot: null,
                tuples: source,
                memberIndex: 0,
                index: 0
            };
        }

        var targetMembers = result.tuple.members;
        var sourceMembers = source[0].members;
        var memberIndex = -1;

        if (targetMembers.length !== sourceMembers.length) {
            return {
                parsedRoot: null,
                tuples: source,
                memberIndex: 0,
                index: 0
            };
        }

        for (var idx = 0, length = targetMembers.length; idx < length; idx++) {
            if (!targetMembers[idx].measure && sourceMembers[idx].children[0]) {
                if (memberIndex == -1 && sourceMembers[idx].children.length) {
                    memberIndex = idx;
                }

                targetMembers[idx].children = sourceMembers[idx].children;
            }
        }

        measures = Math.max(measures.length, 1);

        return {
            parsedRoot: result.tuple,
            index: result.index * measures,
            memberIndex: memberIndex,
            tuples: target
        };
    }

    function equalTuples(first, second) {
        var equal = true;
        var idx, length;
        var name;

        first = first.members;
        second = second.members;

        for (idx = 0, length = first.length; idx < length; idx++) {
            if (first[idx].measure || second[idx].measure) {
                continue;
            }

            equal = equal && (getName(first[idx]) === getName(second[idx]));
        }

        return equal;
    }

    function findExistingTuple(tuples, toFind) {
        var idx, length, tuple, found, counter = 0;
        var memberIndex, membersLength, member;

        for (idx = 0, length = tuples.length; idx < length; idx++) {
            tuple = tuples[idx];
            if (equalTuples(tuple, toFind)) {
                return {
                    tuple: tuple,
                    index: counter
                };
            }

            counter++;

            for (memberIndex = 0, membersLength = tuple.members.length; memberIndex < membersLength; memberIndex++) {
                member = tuple.members[memberIndex];
                if (member.measure) {
                    //counter += member.children.length;
                    continue;
                }
                found = findExistingTuple(member.children, toFind);
                counter += found.index;
                if (found.tuple) {
                    return {
                        tuple: found.tuple,
                        index: counter
                    };
                }
            }
        }

        return {
            index: counter
        };
    }

    function addMembers(members, map) {
        var member, i, len, path = "";
        for (i = 0, len = members.length; i < len; i++) {
            member = members[i];
            path += member.name;
            if (!map[path]) {
                map[path] = member;
            }
        }
    }

    function findParentMember(tuple, map) {
        var members = tuple.members;
        var i, len, member, path = "";
        var parentPath = "";
        var parentMember;

        for (i = 0, len = members.length; i < len; i++) {
            member = members[i];
            if (parentMember) {
                if (map[path + member.name]) {
                    path += member.name;
                    parentMember = map[path];
                    continue;
                } else if (map[path + member.parentName]) {
                    return map[path + member.parentName];
                } else {
                    if (member.parentName) {
                        parentPath += member.parentName;
                    }
                    return map[parentPath];
                }
            }

            path += member.name;
            parentMember = map[member.parentName];

            if (!parentMember) {
                parentMember = map[path];
                if (!parentMember) {
                    return null;
                }
            }

            if (parentMember) {
                parentPath += parentMember.name;
            }
        }

        return parentMember;
    }

    function measurePosition(tuple, measures) {
        if (measures.length === 0) {
            return -1;
        }

        var measure = measures[0];
        var members = tuple.members;
        for (var idx = 0, len = members.length; idx < len; idx++) {
            if (members[idx].name == measure.name) {
                return idx;
            }
        }
    }

    function normalizeTupleMeasures(tuple, index) {
        if (index < 0) {
            return;
        }
        var member = {
            name: "Measures",
            measure: true,
            children: [
                $.extend({ members: [], dataIndex: tuple.dataIndex }, tuple.members[index])
            ]
        };
        tuple.members.splice(index, 1, member);
        tuple.dataIndex = undefined;
    }

    function parseSource(tuples, measures) {
        if (tuples.length < 1) {
            return [];
        }
        var result = [];
        var map = {};
        var measureIndex = measurePosition(tuples[0], measures);

        for (var i = 0; i < tuples.length; i++) {
            var tuple = tuples[i];

            //keep the old data index of the tuple
            tuple.dataIndex = i;

            normalizeTupleMeasures(tuple, measureIndex);
            var parentMember = findParentMember(tuple, map);

            if (parentMember) {
                if (measureIndex < 0 || !parentMember.measure) {
                    parentMember.children.push(tuple);
                } else {
                    parentMember.children.push(tuple.members[measureIndex].children[0]);
                }
            } else {
                result.push(tuple);
            }

            addMembers(tuple.members, map);
        }

        return result;
    }

    function prepareDataOnRows(tuples, data) {
        if (!tuples || !tuples.length) {
            return data;
        }

        var result = [];
        var indices = buildDataIndices(tuples);
        var rowsLength = indices.length;
        var columnsLength = Math.max(data.length / rowsLength, 1);
        var rowIndex, columnIndex, targetIndex, sourceIndex;
        var calcIndex;

        for (rowIndex = 0; rowIndex < rowsLength; rowIndex++) {
            targetIndex = columnsLength * rowIndex;
            sourceIndex = columnsLength * indices[rowIndex];
            for (columnIndex = 0; columnIndex < columnsLength; columnIndex++) {
                calcIndex = parseInt(sourceIndex + columnIndex, 10);
                result[parseInt(targetIndex + columnIndex, 10)] = data[calcIndex] || { value: "", fmtValue: "", ordinal: calcIndex };
            }
        }

        return result;
    }

    function prepareDataOnColumns(tuples, data, rootAdded) {
        if (!tuples || !tuples.length) {
            return data;
        }

        var result = [];
        var indices = buildDataIndices(tuples);
        var columnsLength = indices.length;
        var rowsLength = Math.max(data.length / columnsLength, 1);
        var columnIndex, rowIndex, dataIndex, calcIndex;

        for (rowIndex = 0; rowIndex < rowsLength; rowIndex++) {
            dataIndex = columnsLength * rowIndex;
            for (columnIndex = 0; columnIndex < columnsLength; columnIndex++) {
                calcIndex = indices[columnIndex] + dataIndex;
                result[dataIndex + columnIndex] = data[calcIndex] || { value: "", fmtValue: "", ordinal: calcIndex };
            }
        }

        return result;
    }

    function buildDataIndices(tuples) {
        tuples = tuples.slice();
        var result = [];
        var tuple = tuples.shift();
        var idx, length, spliceIndex, children, member;

        while (tuple) {
            //required for multiple measures
            if (tuple.dataIndex !== undefined) {
                result.push(tuple.dataIndex);
            }

            spliceIndex = 0;
            for (idx = 0, length = tuple.members.length; idx < length; idx++) {
                member = tuple.members[idx];
                children = member.children;
                if (member.measure) {
                    [ ].splice.apply(tuples, [0, 0].concat(children));
                } else {
                    [ ].splice.apply(tuples, [spliceIndex, 0].concat(children));
                }
                spliceIndex += children.length;
            }

            tuple = tuples.shift();
        }

        return result;
    }

    PivotDataSource.create = function (options) {
        options = options && options.push ? { data: options} : options;

        var dataSource = options || {},
            data = dataSource.data;

        dataSource.data = data;

        if (!(dataSource instanceof PivotDataSource) && dataSource instanceof kendo.data.DataSource) {
            throw new Error("Incorrect DataSource type. Only PivotDataSource instances are supported");
        }

        return dataSource instanceof PivotDataSource ? dataSource : new PivotDataSource(dataSource);
    };

    function baseHierarchyPath(memberName) {
        var parts = memberName.split(".");
        if (parts.length > 2) {
            return parts[0] + "." + parts[1];
        }
        return memberName;
    }

    function expandMemberDescriptor(names, sort) {
        var idx = names.length - 1;
        var name = names[idx];
        var sortDescriptor;

        sortDescriptor = sortDescriptorForMember(sort, name);

        if (sortDescriptor && sortDescriptor.dir) {
            name = "ORDER(" + name + ".Children," + sortDescriptor.field + ".CurrentMember.MEMBER_CAPTION," + sortDescriptor.dir + ")";
        } else {
            name += ".Children";
        }

        names[idx] = name;

        return names;
    }

    function sortDescriptorForMember(sort, member) {
        for (var idx = 0, length = sort.length; idx < length; idx++) {
            if (member.indexOf(sort[idx].field) === 0) {
                return sort[idx];
            }
        }
        return null;
    }

    function crossJoin(names) {
        var result = "CROSSJOIN({";
        var r;

        if (names.length > 2) {
            r = names.pop();
            result += crossJoin(names);
        } else {
            result += names.shift();
            r = names.pop();
        }

        result += "},{";
        result += r;
        result += "})";
        return result;
    }

    function crossJoinCommand(members, measures) {
        var tmp = members.slice(0);
        var names;

        if (measures.length > 1) {
            tmp.push("{" + measureNames(measures).join(",") + "}");
        }
        return crossJoin(tmp);
    }

    function expandedMembers(members) {
        var result = [];

        for (var idx = 0; idx < members.length; idx++) {
            if (members[idx].expand) {
                result.push(members[idx]);
            }
        }

        return result;
    }

    function measureNames(measures) {
        var idx = 0;
        var length = measures.length;
        var result = [];
        var measure;

        for (; idx < length; idx++) {
            measure = measures[idx];
            result.push(measure.name !== undefined ? measure.name : measure);
        }

        return result;
    }

    function getName(name) {
        name = name.name || name;

        if (toString.call(name) === "[object Array]") {
            name = name[name.length - 1];
        }

        return name;
    }

    function getRootNames(members) {
        var length = members.length;
        var names = [];
        var idx = 0;

        for (; idx < length; idx++) {
            names.push(members[idx].name[0]);
        }

        return names;
    }

    function mapNames(names, rootNames) {
        var name;
        var rootName;

        var j;
        var idx = 0;
        var length = names.length;
        var rootLength = rootNames.length;

        rootNames = rootNames.slice(0);

        for (; idx < length; idx++) {
            name = names[idx];

            for (j = 0; j < rootLength; j++) {
                rootName = baseHierarchyPath(rootNames[j]);

                if (name.indexOf(rootName) !== -1) {
                    rootNames[j] = name;
                    break;
                }
            }
        }

        return rootNames;
    }

    function parseDescriptors(members) {
        var expanded = [];
        var child = [];
        var root = [];
        var member;

        var j, l;
        var idx = 0;
        var length = members.length;

        var name;
        var hierarchyName;

        var found;

        for (; idx < length; idx++) {
            member = members[idx];
            name = member.name;
            found = false;

            if (toString.call(name) !== "[object Array]") {
                member.name = name = [name];
            }

            if (name.length > 1) {
                child.push(member);
            } else {
                hierarchyName = baseHierarchyPath(name[0]);

                for (j = 0, l = root.length; j < l; j++) {
                    if (root[j].name[0].indexOf(hierarchyName) === 0) {
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    root.push(member);
                }

                if (member.expand) {
                    expanded.push(member);
                }
            }
        }

        expanded = expanded.concat(child);

        return {
            root: root,
            expanded: expanded
        };
    }

    function serializeMembers(members, measures, sort) {
        var command = "";

        members = members || [];

        var expanded = parseDescriptors(members);
        var root = expanded.root;

        var rootNames = getRootNames(root);
        var crossJoinCommands = [];

        expanded = expanded.expanded;

        var length = expanded.length;
        var idx = 0;

        var memberName;
        var names = [];

        if (rootNames.length > 1 || measures.length > 1) {
            crossJoinCommands.push(crossJoinCommand(rootNames, measures));

            for (; idx < length; idx++) {
                memberName = expandMemberDescriptor(expanded[idx].name, sort);
                names = mapNames(memberName, rootNames);

                crossJoinCommands.push(crossJoinCommand(names, measures));
            }

            command += crossJoinCommands.join(",");
        } else {
            for (; idx < length; idx++) {
                memberName = expandMemberDescriptor(expanded[idx].name, sort);
                names.push(memberName[0]); //check if this is ok
            }

            command += rootNames.concat(names).join(",");
        }

        return command;
    }

    var filterFunctionFormats = {
        contains: ", InStr({0}.CurrentMember.MEMBER_CAPTION,\"{1}\") > 0",
        doesnotcontain: ", InStr({0}.CurrentMember.MEMBER_CAPTION,\"{1}\") = 0",
        startswith: ", Left({0}.CurrentMember.MEMBER_CAPTION,Len(\"{1}\"))=\"{1}\"",
        endswith: ", Right({0}.CurrentMember.MEMBER_CAPTION,Len(\"{1}\"))=\"{1}\"",
        eq: ", {0}.CurrentMember.MEMBER_CAPTION = \"{1}\"",
        neq: ", NOT {0}.CurrentMember.MEMBER_CAPTION = \"{1}\""
    };

    function serializeExpression(expression) {
        var command = "";
        var value = expression.value;
        var field = expression.field;
        var operator = expression.operator;

        if (operator == "in") {
            command += "{";
            command += value;
            command += "}";
        } else {
            command += "Filter(";
            command += field + ".Children";
            command += kendo.format(filterFunctionFormats[operator], field, value);
            command += ")";
        }

        return command;
    }

    function serializeFilters(filter, cube) {
        var command = "", current;
        var filters = filter.filters;
        var length = filters.length;
        var idx;

        for (idx = length - 1; idx >= 0; idx--) {

            current = "SELECT (";
            current += serializeExpression(filters[idx]);
            current += ") ON 0";

            if (idx == length - 1) {
                current += " FROM [" + cube + "]";
                command = current;
            } else {
                command = current + " FROM ( " + command + " )";
            }
        }

        return command;
    }

    function serializeOptions(parentTagName, options, capitalize) {
        var result = "";

        if (options) {
            result += "<" + parentTagName + ">";
            var value;
            for (var key in options) {
                value = options[key];
                if (capitalize) {
                    key = key.replace(/([A-Z]+(?=$|[A-Z][a-z])|[A-Z]?[a-z]+)/g, "$1_").toUpperCase().replace(/_$/, "");
                }
                result += "<" + key + ">" + value + "</" + key + ">";
            }
            result += "</" + parentTagName + ">";
        } else {
            result += "<" + parentTagName + "/>";
        }
        return result;
    }

    var xmlaDiscoverCommands = {
        schemaCubes: "MDSCHEMA_CUBES",
        schemaCatalogs: "DBSCHEMA_CATALOGS",
        schemaMeasures: "MDSCHEMA_MEASURES",
        schemaDimensions: "MDSCHEMA_DIMENSIONS",
        schemaHierarchies: "MDSCHEMA_HIERARCHIES",
        schemaLevels: "MDSCHEMA_LEVELS",
        schemaMembers: "MDSCHEMA_MEMBERS",
        schemaKPIs: "MDSCHEMA_KPIS"
    };

    var convertersMap = {
        read: function (options, type) {
            var command = '<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"><Header/><Body><Execute xmlns="urn:schemas-microsoft-com:xml-analysis"><Command><Statement>';

            command += "SELECT NON EMPTY {";

            var columns = options.columns || [];
            var rows = options.rows || [];

            var measures = options.measures || [];
            var measuresRowAxis = options.measuresAxis === "rows";
            var sort = options.sort || [];

            if (!columns.length && rows.length && (!measures.length || (measures.length && measuresRowAxis))) {
                columns = rows;
                rows = [];
                measuresRowAxis = false;
            }

            if (!columns.length && !rows.length) {
                measuresRowAxis = false;
            }

            if (columns.length) {
                command += serializeMembers(columns, !measuresRowAxis ? measures : [], sort);
            } else if (measures.length && !measuresRowAxis) {
                command += measureNames(measures).join(",");
            }

            command += "} DIMENSION PROPERTIES CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON COLUMNS";

            if (rows.length || (measuresRowAxis && measures.length > 1)) {
                command += ", NON EMPTY {";

                if (rows.length) {
                    command += serializeMembers(rows, measuresRowAxis ? measures : [], sort);
                } else {
                    command += measureNames(measures).join(",");
                }

                command += "} DIMENSION PROPERTIES CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON ROWS";
            }

            if (options.filter) {
                command += " FROM ";
                command += "(";
                command += serializeFilters(options.filter, options.connection.cube);
                command += ")";
            } else {
                command += " FROM [" + options.connection.cube + "]";
            }

            if (measures.length == 1 && columns.length) {
                command += " WHERE (" + measureNames(measures).join(",") + ")";
            }

            command += '</Statement></Command><Properties><PropertyList><Catalog>' + options.connection.catalog + '</Catalog><Format>Multidimensional</Format></PropertyList></Properties></Execute></Body></Envelope>';
            return command.replace(/\&/g, "&amp;");
        },
        discover: function (options, type) {
            options = options || {};

            var command = '<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"><Header/><Body><Discover xmlns="urn:schemas-microsoft-com:xml-analysis">';
            command += "<RequestType>" + (xmlaDiscoverCommands[options.command] || options.command) + "</RequestType>";

            command += "<Restrictions>" + serializeOptions("RestrictionList", options.restrictions, true) + "</Restrictions>";

            if (options.connection && options.connection.catalog) {
                options.properties = $.extend({}, {
                    Catalog: options.connection.catalog
                }, options.properties);
            }

            command += "<Properties>" + serializeOptions("PropertyList", options.properties) + "</Properties>";

            command += '</Discover></Body></Envelope>';
            return command;
        }
    };

    var XmlaTransport = kendo.data.RemoteTransport.extend({
        init: function (options) {
            var originalOptions = options;

            options = this.options = extend(true, {}, this.options, options);

            kendo.data.RemoteTransport.call(this, options);

            if (isFunction(originalOptions.discover)) {
                this.discover = originalOptions.discover;
            } else if (typeof originalOptions.discover === "string") {
                this.options.discover = {
                    url: originalOptions.discover
                };
            } else if (!originalOptions.discover) {
                this.options.discover = this.options.read;
            }
        },
        setup: function (options, type) {
            options.data = options.data || {};
            $.extend(true, options.data, { connection: this.options.connection });

            return kendo.data.RemoteTransport.fn.setup.call(this, options, type);
        },
        options: {
            read: {
                dataType: "text",
                contentType: "text/xml",
                type: "POST"
            },
            discover: {
                dataType: "text",
                contentType: "text/xml",
                type: "POST"
            },
            parameterMap: function (options, type) {
                return convertersMap[type](options, type);
            }
        },

        discover: function (options) {
            return $.ajax(this.setup(options, "discover"));
        }
    });

    function asArray(object) {
        if (object == null) {
            return [];
        }

        var type = toString.call(object);
        if (type !== "[object Array]") {
            return [object];
        }

        return object;
    }

    function translateAxis(axis) {
        var result = { tuples: [] };
        var tuples = asArray(kendo.getter("Tuples.Tuple", true)(axis));
        var captionGetter = kendo.getter("Caption['#text']");
        var unameGetter = kendo.getter("UName['#text']");
        var levelNameGetter = kendo.getter("LName['#text']");
        var levelNumGetter = kendo.getter("LNum['#text']");
        var childrenGetter = kendo.getter("CHILDREN_CARDINALITY['#text']", true);
        var hierarchyGetter = kendo.getter("['@Hierarchy']");
        var parentNameGetter = kendo.getter("PARENT_UNIQUE_NAME['#text']", true);

        for (var idx = 0; idx < tuples.length; idx++) {
            var members = [];
            var member = asArray(tuples[idx].Member);
            for (var memberIdx = 0; memberIdx < member.length; memberIdx++) {
                members.push({
                    children: [],
                    caption: captionGetter(member[memberIdx]),
                    name: unameGetter(member[memberIdx]),
                    levelName: levelNameGetter(member[memberIdx]),
                    levelNum: levelNumGetter(member[memberIdx]),
                    hasChildren: parseInt(childrenGetter(member[memberIdx]), 10) > 0,
                    parentName: parentNameGetter(member[memberIdx]),
                    hierarchy: hierarchyGetter(member[memberIdx])
                });
            }

            result.tuples.push({ members: members });
        }
        return result;
    }

    var schemaDataReaderMap = {
        cubes: {
            name: kendo.getter("CUBE_NAME['#text']", true),
            caption: kendo.getter("CUBE_CAPTION['#text']", true),
            description: kendo.getter("DESCRIPTION['#text']", true),
            type: kendo.getter("CUBE_TYPE['#text']", true)
        },
        catalogs: {
            name: kendo.getter("CATALOG_NAME['#text']", true),
            description: kendo.getter("DESCRIPTION['#text']", true)
        },
        measures: {
            name: kendo.getter("MEASURE_NAME['#text']", true),
            caption: kendo.getter("MEASURE_CAPTION['#text']", true),
            uniqueName: kendo.getter("MEASURE_UNIQUE_NAME['#text']", true),
            description: kendo.getter("DESCRIPTION['#text']", true),
            aggregator: kendo.getter("MEASURE_AGGREGATOR['#text']", true),
            groupName: kendo.getter("MEASUREGROUP_NAME['#text']", true),
            displayFolder: kendo.getter("MEASURE_DISPLAY_FOLDER['#text']", true),
            defaultFormat: kendo.getter("DEFAULT_FORMAT_STRING['#text']", true)
        },
        kpis: {
            name: kendo.getter("KPI_NAME['#text']", true),
            caption: kendo.getter("KPI_CAPTION['#text']", true),
            value: kendo.getter("KPI_VALUE['#text']", true),
            goal: kendo.getter("KPI_GOAL['#text']", true),
            status: kendo.getter("KPI_STATUS['#text']", true),
            trend: kendo.getter("KPI_TREND['#text']", true),
            statusGraphic: kendo.getter("KPI_STATUS_GRAPHIC['#text']", true),
            trendGraphic: kendo.getter("KPI_TREND_GRAPHIC['#text']", true),
            description: kendo.getter("KPI_DESCRIPTION['#text']", true),
            groupName: kendo.getter("MEASUREGROUP_NAME['#text']", true)
        },
        dimensions: {
            name: kendo.getter("DIMENSION_NAME['#text']", true),
            caption: kendo.getter("DIMENSION_CAPTION['#text']", true),
            description: kendo.getter("DESCRIPTION['#text']", true),
            uniqueName: kendo.getter("DIMENSION_UNIQUE_NAME['#text']", true),
            defaultHierarchy: kendo.getter("DEFAULT_HIERARCHY['#text']", true),
            type: kendo.getter("DIMENSION_TYPE['#text']", true)
            //unknown = 0; time = 1; measure = 2; other = 3; quantitative = 5; accounts = 6; customers = 7; products = 8; scenario = 9; utility = 10; currency = 11; rates = 12; channel = 13; promotion = 14; organization = 15; billOfMaterials = 16; geography = 17;

        },
        hierarchies: {
            name: kendo.getter("HIERARCHY_NAME['#text']", true),
            caption: kendo.getter("HIERARCHY_CAPTION['#text']", true),
            description: kendo.getter("DESCRIPTION['#text']", true),
            uniqueName: kendo.getter("HIERARCHY_UNIQUE_NAME['#text']", true),
            dimensionUniqueName: kendo.getter("DIMENSION_UNIQUE_NAME['#text']", true),
            displayFolder: kendo.getter("HIERARCHY_DISPLAY_FOLDER['#text']", true),
            origin: kendo.getter("HIERARCHY_ORIGIN['#text']", true),
            defaultMember: kendo.getter("DEFAULT_MEMBER['#text']", true)
        },
        levels: {
            name: kendo.getter("LEVEL_NAME['#text']", true),
            caption: kendo.getter("LEVEL_CAPTION['#text']", true),
            description: kendo.getter("DESCRIPTION['#text']", true),
            uniqueName: kendo.getter("LEVEL_UNIQUE_NAME['#text']", true),
            dimensionUniqueName: kendo.getter("DIMENSION_UNIQUE_NAME['#text']", true),
            displayFolder: kendo.getter("LEVEL_DISPLAY_FOLDER['#text']", true),
            orderingProperty: kendo.getter("LEVEL_ORDERING_PROPERTY['#text']", true),
            origin: kendo.getter("LEVEL_ORIGIN['#text']", true),
            hierarchyUniqueName: kendo.getter("HIERARCHY_UNIQUE_NAME['#text']", true)
        },
        members: {
            name: kendo.getter("MEMBER_NAME['#text']", true),
            caption: kendo.getter("MEMBER_CAPTION['#text']", true),
            uniqueName: kendo.getter("MEMBER_UNIQUE_NAME['#text']", true),
            dimensionUniqueName: kendo.getter("DIMENSION_UNIQUE_NAME['#text']", true),
            hierarchyUniqueName: kendo.getter("HIERARCHY_UNIQUE_NAME['#text']", true),
            levelUniqueName: kendo.getter("LEVEL_UNIQUE_NAME['#text']", true),
            childrenCardinality: kendo.getter("CHILDREN_CARDINALITY['#text']", true)
        }
    };

    var xmlaReaderMethods = ["axes", "catalogs", "cubes", "dimensions", "hierarchies", "levels", "measures"];

    var XmlaDataReader = kendo.data.XmlDataReader.extend({
        init: function (options) {
            kendo.data.XmlDataReader.call(this, options);

            this._extend(options);
        },
        _extend: function (options) {
            var idx = 0;
            var length = xmlaReaderMethods.length;
            var methodName;
            var option;

            for (; idx < length; idx++) {
                methodName = xmlaReaderMethods[idx];
                option = options[methodName];

                if (option && option !== identity) {
                    this[methodName] = option;
                }
            }
        },
        parse: function (xml) {
            var result = kendo.data.XmlDataReader.fn.parse(xml.replace(/<(\/?)(\w|-)+:/g, "<$1"));
            return kendo.getter("['Envelope']['Body']", true)(result);
        },
        errors: function (root) {
            var fault = kendo.getter("['Fault']", true)(root);
            if (fault) {
                return [{
                    faultstring: kendo.getter("faultstring['#text']", true)(fault),
                    faultcode: kendo.getter("faultcode['#text']", true)(fault)
                }];
            }
            return null;
        },
        axes: function (root) {
            root = kendo.getter("ExecuteResponse.return.root", true)(root);

            var axes = asArray(kendo.getter("Axes.Axis", true)(root));
            var columns = translateAxis(axes[0]);
            var rows = {};

            if (axes.length > 2) {
                rows = translateAxis(axes[1]);
            }

            return {
                columns: columns,
                rows: rows
            };
        },
        data: function (root) {
            root = kendo.getter("ExecuteResponse.return.root", true)(root);

            var cells = asArray(kendo.getter("CellData.Cell", true)(root));

            var result = [];
            var ordinalGetter = kendo.getter("['@CellOrdinal']");
            var valueGetter = kendo.getter("Value['#text']");
            var fmtValueGetter = kendo.getter("FmtValue['#text']");

            for (var idx = 0; idx < cells.length; idx++) {
                result.push({
                    value: valueGetter(cells[idx]),
                    fmtValue: fmtValueGetter(cells[idx]),
                    ordinal: parseInt(ordinalGetter(cells[idx]), 10)
                });
            }

            return result;
        },
        _mapSchema: function (root, getters) {
            root = kendo.getter("DiscoverResponse.return.root", true)(root);
            var rows = asArray(kendo.getter("row", true)(root));

            var result = [];

            for (var idx = 0; idx < rows.length; idx++) {
                var obj = {};
                for (var key in getters) {
                    obj[key] = getters[key](rows[idx]);
                }
                result.push(obj);
            }

            return result;
        },
        measures: function (root) {
            return this._mapSchema(root, schemaDataReaderMap.measures);
        },
        kpis: function (root) {
            return this._mapSchema(root, schemaDataReaderMap.kpis);
        },
        hierarchies: function (root) {
            return this._mapSchema(root, schemaDataReaderMap.hierarchies);
        },
        levels: function (root) {
            return this._mapSchema(root, schemaDataReaderMap.levels);
        },
        dimensions: function (root) {
            return this._mapSchema(root, schemaDataReaderMap.dimensions);
        },
        cubes: function (root) {
            return this._mapSchema(root, schemaDataReaderMap.cubes);
        },
        catalogs: function (root) {
            return this._mapSchema(root, schemaDataReaderMap.catalogs);
        },
        members: function (root) {
            return this._mapSchema(root, schemaDataReaderMap.members);
        }
    });

    extend(true, kendo.data, {
        PivotDataSource: PivotDataSource,
        XmlaTransport: XmlaTransport,
        XmlaDataReader: XmlaDataReader,
        PivotCubeBuilder: PivotCubeBuilder,
        transports: {
            xmla: XmlaTransport
        },
        readers: {
            xmla: XmlaDataReader
        }
    });

    var sortExpr = function (expressions, name) {
        if (!expressions) {
            return null;
        }

        for (var idx = 0, length = expressions.length; idx < length; idx++) {
            if (expressions[idx].field === name) {
                return expressions[idx];
            }
        }

        return null;
    };

    var removeExpr = function (expressions, name) {
        var result = [];

        for (var idx = 0, length = expressions.length; idx < length; idx++) {
            if (expressions[idx].field !== name) {
                result.push(expressions[idx]);
            }
        }

        return result;
    };

    kendo.ui.PivotSettingTarget = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            that.element.addClass("k-pivot-setting");

            that.dataSource = kendo.data.PivotDataSource.create(options.dataSource);

            that._refreshHandler = $.proxy(that.refresh, that);
            that.dataSource.first(CHANGE, that._refreshHandler);

            if (!options.template) {
                that.options.template = "<div data-" + kendo.ns + 'name="${data.name || data}">${data.name || data}' +
                    (that.options.enabled ?
                    '<a class="k-button k-button-icon k-button-bare"><span class="k-icon k-setting-delete"></span></a>' : "") + '</div>';
            }

            that.template = kendo.template(that.options.template);
            that.emptyTemplate = kendo.template(that.options.emptyTemplate);

            that._sortable();

            that.element.on("click" + NS, ".k-button,.k-item", function (e) {
                var target = $(e.target);
                var name = target.closest("[" + kendo.attr("name") + "]")
                                 .attr(kendo.attr("name"));

                if (!name) {
                    return;
                }

                if (target.hasClass("k-setting-delete")) {
                    that.remove(name);
                } else if (that.options.sortable && target[0] === e.currentTarget) {
                    that.sort({
                        field: name,
                        dir: target.find(".k-i-sort-asc")[0] ? "desc" : "asc"
                    });
                }
            });

            if (options.filterable || options.sortable) {
                that.fieldMenu = new ui.PivotFieldMenu(that.element, {
                    messages: that.options.messages.fieldMenu,
                    filter: ".k-setting-fieldmenu",
                    filterable: options.filterable,
                    sortable: options.sortable,
                    dataSource: that.dataSource
                });
            }

            that.refresh();
        },

        options: {
            name: "PivotSettingTarget",
            template: null,
            filterable: false,
            sortable: false,
            emptyTemplate: "<div class='k-empty'>${data}</div>",
            setting: "columns",
            enabled: true,
            messages: {
                empty: "Drop Fields Here"
            }
        },
        setDataSource: function (dataSource) {
            this.dataSource.unbind(CHANGE, this._refreshHandler);
            this.dataSource = this.options.dataSource = dataSource;

            if (this.fieldMenu) {
                this.fieldMenu.setDataSource(dataSource);
            }
            dataSource.first(CHANGE, this._refreshHandler);

            this.refresh();
        },

        _sortable: function () {
            var that = this;

            if (that.options.enabled) {
                this.sortable = this.element.kendoSortable({
                    connectWith: this.options.connectWith,
                    filter: ">:not(.k-empty)",
                    hint: that.options.hint,
                    cursor: "move",
                    start: function (e) {
                        e.item.focus().blur();
                    },
                    change: function (e) {
                        var name = e.item.attr(kendo.attr("name"));

                        if (e.action == "receive") {
                            that.add(name);
                        } else if (e.action == "remove") {
                            that.remove(name);
                        } else if (e.action == "sort") {
                            that.move(name, e.newIndex);
                        }
                    }
                }).data("kendoSortable");
            }
        },

        _indexOf: function (name, items) {
            var idx, length, index = -1;

            for (idx = 0, length = items.length; idx < length; idx++) {
                if (getName(items[idx]) === name) {
                    index = idx;
                    break;
                }
            }
            return index;
        },

        _isKPI: function (data) {
            return data.type === "kpi" || data.measure;
        },

        validate: function (data) {
            var isMeasure = (data.type == 2 || "aggregator" in data || this._isKPI(data));

            if (isMeasure) {
                return this.options.setting === "measures";
            }

            if (this.options.setting === "measures") {
                return isMeasure;
            }

            var items = this.dataSource[this.options.setting]();
            var name = data.defaultHierarchy || data.uniqueName;
            if (this._indexOf(name, items) > -1) {
                return false;
            }

            items = this.dataSource[this.options.setting === "columns" ? "rows" : "columns"]();
            if (this._indexOf(name, items) > -1) {
                return false;
            }

            return true;
        },

        add: function (name) {
            var items = this.dataSource[this.options.setting]();
            var i, l;
            var idx;

            name = $.isArray(name) ? name.slice(0) : [name];

            for (i = 0, l = name.length; i < l; i++) {
                if (this._indexOf(name[i], items) !== -1) {
                    name.splice(i, 1);
                    i -= 1;
                    l -= 1;
                }
            }

            if (name.length) {
                items = items.concat(name);
                this.dataSource[this.options.setting](items);
            }
        },

        move: function (name, index) {
            var items = this.dataSource[this.options.setting]();
            var idx = this._indexOf(name, items);

            if (idx > -1) {
                name = items.splice(idx, 1)[0];

                items.splice(index, 0, name);

                this.dataSource[this.options.setting](items);
            }
        },

        remove: function (name) {
            var items = this.dataSource[this.options.setting]();

            var idx = this._indexOf(name, items);
            if (idx > -1) {
                items.splice(idx, 1);
                this.dataSource[this.options.setting](items);
            }
        },

        sort: function (expr) {
            var sortable = this.options.sortable;
            var allowUnsort = sortable === true || sortable.allowUnsort;
            var skipExpr = allowUnsort && expr.dir === "asc";

            var expressions = (this.dataSource.sort() || []);
            var result = removeExpr(expressions, expr.field);

            if (skipExpr && expressions.length !== result.length) {
                expr = null;
            }

            if (expr) {
                result.push(expr);
            }

            this.dataSource.sort(result);
        },

        refresh: function () {
            var html = "";
            var items = this.dataSource[this.options.setting]();
            var length = items.length;
            var idx = 0;
            var item;

            if (length) {
                for (; idx < length; idx++) {
                    item = items[idx];
                    item = item.name === undefined ? { name: item} : item;

                    html += this.template(extend({ sortIcon: this._sortIcon(item.name) }, item));
                }
            } else {
                html = this.emptyTemplate(this.options.messages.empty);
            }

            this.element.html(html);
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            this.dataSource.unbind(CHANGE, this._refreshHandler);
            this.element.off(NS);

            if (this.sortable) {
                this.sortable.destroy();
            }

            if (this.fieldMenu) {
                this.fieldMenu.destroy();
            }

            this.element = null;
            this._refreshHandler = null;
        },

        _sortIcon: function (name) {
            var expressions = this.dataSource.sort();
            var expr = sortExpr(expressions, getName(name));
            var icon = "";

            if (expr) {
                icon = "k-i-sort-" + expr.dir;
            }

            return icon;
        }
    });

    var PivotGrid = Widget.extend({
        init: function (element, options) {
            var that = this;
            var columnBuilder;
            var rowBuilder;

            Widget.fn.init.call(that, element, options);

            that._dataSource();

            that._bindConfigurator();

            that._wrapper();
            that._createLayout();

            that._columnBuilder = columnBuilder = new ColumnBuilder();
            that._rowBuilder = rowBuilder = new RowBuilder();
            that._contentBuilder = new ContentBuilder();

            that._templates();

            that.columnsHeader
                .add(that.rowsHeader)
                .on("click", "span.k-icon", function () {
                    var button = $(this);
                    var builder = columnBuilder;
                    var action = "expandColumn";
                    var eventName;
                    var path = button.attr(kendo.attr("path"));
                    var eventArgs = {
                        axis: "columns",
                        path: $.parseJSON(path)
                    };

                    if (button.parent().is("td")) {
                        builder = rowBuilder;
                        action = "expandRow";
                        eventArgs.axis = "rows";
                    }

                    var expanded = button.hasClass(STATE_EXPANDED);
                    var metadata = builder.metadata[path];
                    var request = metadata.expanded === undefined;

                    eventName = expanded ? COLLAPSEMEMBER : EXPANDMEMBER;
                    if (that.trigger(eventName, eventArgs)) {
                        return;
                    }

                    builder.metadata[path].expanded = !expanded;

                    button.toggleClass(STATE_EXPANDED, !expanded)
                          .toggleClass(STATE_COLLAPSED, expanded);

                    if (!expanded && request) {
                        that.dataSource[action](eventArgs.path);
                    } else {
                        that.refresh();
                    }
                });

            that._scrollable();

            if (that.options.autoBind) {
                that.dataSource.fetch();
            }

            kendo.notify(that);
        },

        events: [
            DATABINDING,
            DATABOUND,
            EXPANDMEMBER,
            COLLAPSEMEMBER
        ],

        options: {
            name: "PivotGrid",
            autoBind: true,
            reorderable: true,
            filterable: false,
            sortable: false,
            height: null,
            columnWidth: 100,
            configurator: "",
            columnHeaderTemplate: null,
            rowHeaderTemplate: null,
            dataCellTemplate: null,
            kpiStatusTemplate: null,
            kpiTrendTemplate: null,
            messages: {
                measureFields: "Drop Data Fields Here",
                columnFields: "Drop Column Fields Here",
                rowFields: "Drop Rows Fields Here"
            }
        },

        _templates: function () {
            var columnTemplate = this.options.columnHeaderTemplate;
            var rowTemplate = this.options.rowHeaderTemplate;
            var dataTemplate = this.options.dataCellTemplate;
            var kpiStatusTemplate = this.options.kpiStatusTemplate;
            var kpiTrendTemplate = this.options.kpiTrendTemplate;

            this._columnBuilder.template = kendo.template(columnTemplate || HEADER_TEMPLATE, { useWithBlock: !!columnTemplate });
            this._contentBuilder.dataTemplate = kendo.template(dataTemplate || DATACELL_TEMPLATE, { useWithBlock: !!dataTemplate });
            this._contentBuilder.kpiStatusTemplate = kendo.template(kpiStatusTemplate || KPISTATUS_TEMPLATE, { useWithBlock: !!kpiStatusTemplate });
            this._contentBuilder.kpiTrendTemplate = kendo.template(kpiTrendTemplate || KPITREND_TEMPLATE, { useWithBlock: !!kpiTrendTemplate });
            this._rowBuilder.template = kendo.template(rowTemplate || HEADER_TEMPLATE, { useWithBlock: !!rowTemplate });
        },

        _bindConfigurator: function () {
            var configurator = this.options.configurator;
            if (configurator) {
                $(configurator).kendoPivotConfigurator("setDataSource", this.dataSource);
            }
        },

        cellInfoByElement: function (element) {
            element = $(element);

            return this.cellInfo(element.index(), element.parent("tr").index());
        },

        cellInfo: function (columnIndex, rowIndex) {
            var contentBuilder = this._contentBuilder;
            var columnInfo = contentBuilder.columnIndexes[columnIndex || 0];
            var rowInfo = contentBuilder.rowIndexes[rowIndex || 0];
            var dataIndex;

            if (!columnInfo || !rowInfo) {
                return null;
            }

            dataIndex = (rowInfo.index * contentBuilder.rowLength) + columnInfo.index;

            return {
                columnTuple: columnInfo.tuple,
                rowTuple: rowInfo.tuple,
                measure: columnInfo.measure || rowInfo.measure,
                dataItem: this.dataSource.view()[dataIndex]
            };
        },

        setDataSource: function (dataSource) {
            this.options.dataSource = dataSource;

            this._dataSource();

            if (this.measuresTarget) {
                this.measuresTarget.setDataSource(dataSource);
            }

            if (this.rowsTarget) {
                this.rowsTarget.setDataSource(dataSource);
            }

            if (this.columnsTarget) {
                this.columnsTarget.setDataSource(dataSource);
            }

            this._bindConfigurator();

            if (this.options.autoBind) {
                dataSource.fetch();
            }
        },

        setOptions: function (options) {
            Widget.fn.setOptions.call(this, options);

            this._templates();
        },

        _dataSource: function () {
            var that = this;
            var dataSource = that.options.dataSource;

            dataSource = $.isArray(dataSource) ? { data: dataSource} : dataSource;

            if (that.dataSource && this._refreshHandler) {
                that.dataSource.unbind(CHANGE, that._refreshHandler)
                               .unbind(STATERESET, that._stateResetHandler)
                               .unbind(PROGRESS, that._progressHandler)
                               .unbind(ERROR, that._errorHandler);
            } else {
                that._refreshHandler = $.proxy(that.refresh, that);
                that._progressHandler = $.proxy(that._requestStart, that);
                that._stateResetHandler = $.proxy(that._stateReset, that);
                that._errorHandler = $.proxy(that._error, that);
            }

            that.dataSource = kendo.data.PivotDataSource.create(dataSource)
                                   .bind(CHANGE, that._refreshHandler)
                                   .bind(PROGRESS, that._progressHandler)
                                   .bind(STATERESET, that._stateResetHandler)
                                   .bind(ERROR, that._errorHandler);
        },

        _error: function () {
            this._progress(false);
        },

        _requestStart: function () {
            this._progress(true);
        },

        _stateReset: function () {
            this._columnBuilder.reset();
            this._rowBuilder.reset();
        },

        _wrapper: function () {
            var height = this.options.height;

            this.wrapper = this.element.addClass("k-widget k-pivot");

            if (height) {
                this.wrapper.css("height", height);
            }
        },

        _measureFields: function () {
            this.measureFields = $(DIV).addClass("k-pivot-toolbar k-header k-settings-measures");

            this.measuresTarget = this._createSettingTarget(this.measureFields, {
                setting: "measures",
                messages: {
                    empty: this.options.messages.measureFields
                }
            });
        },

        _createSettingTarget: function (element, options) {
            var template = '<span tabindex="0" class="k-button" data-' + kendo.ns + 'name="${data.name}">${data.name}';
            var sortable = options.sortable;
            var icons = "";

            if (sortable) {
                icons += '#if (data.sortIcon) {#';
                icons += '<span class="k-icon ${data.sortIcon} k-setting-sort"></span>';
                icons += '#}#';
            }

            if (options.filterable || sortable) {
                icons += '<span class="k-icon k-i-arrowhead-s k-setting-fieldmenu"></span>';
            }
            if (this.options.reorderable) {
                icons += '<span class="k-icon k-si-close k-setting-delete"></span>';
            }

            if (icons) {
                template += '<span class="k-field-actions">' + icons + '</span>';
            }

            template += '</span>';

            return new kendo.ui.PivotSettingTarget(element, $.extend({
                template: template,
                emptyTemplate: '<span class="k-empty">${data}</span>',
                enabled: this.options.reorderable,
                dataSource: this.dataSource
            }, options));
        },

        _initSettingTargets: function () {
            this.columnsTarget = this._createSettingTarget(this.columnFields, {
                connectWith: this.rowFields,
                setting: "columns",
                filterable: this.options.filterable,
                sortable: this.options.sortable,
                messages: {
                    empty: this.options.messages.columnFields,
                    fieldMenu: this.options.messages.fieldMenu
                }
            });

            this.rowsTarget = this._createSettingTarget(this.rowFields, {
                connectWith: this.columnFields,
                setting: "rows",
                filterable: this.options.filterable,
                sortable: this.options.sortable,
                messages: {
                    empty: this.options.messages.rowFields,
                    fieldMenu: this.options.messages.fieldMenu
                }
            });
        },

        _createLayout: function () {
            var that = this;
            var layoutTable = $(LAYOUT_TABLE);
            var leftContainer = layoutTable.find(".k-pivot-rowheaders");
            var rightContainer = layoutTable.find(".k-pivot-table");
            var gridWrapper = $(DIV).addClass("k-grid k-widget");

            that._measureFields();
            that.columnFields = $(DIV).addClass("k-pivot-toolbar k-header k-settings-columns");

            that.rowFields = $(DIV).addClass("k-pivot-toolbar k-header k-settings-rows");
            that.columnsHeader = $('<div class="k-grid-header-wrap" />')
                                    .wrap('<div class="k-grid-header" />');

            that.columnsHeader.parent().css("padding-right", kendo.support.scrollbar());

            that.rowsHeader = $('<div class="k-grid k-widget k-alt"/>');
            that.content = $('<div class="k-grid-content" />');

            leftContainer.append(that.measureFields);
            leftContainer.append(that.rowFields);
            leftContainer.append(that.rowsHeader);

            gridWrapper.append(that.columnsHeader.parent());
            gridWrapper.append(that.content);

            rightContainer.append(that.columnFields);
            rightContainer.append(gridWrapper);

            that.wrapper.append(layoutTable);

            that.columnsHeaderTree = new kendo.dom.Tree(that.columnsHeader[0]);
            that.rowsHeaderTree = new kendo.dom.Tree(that.rowsHeader[0]);
            that.contentTree = new kendo.dom.Tree(that.content[0]);

            that._initSettingTargets();
        },

        _progress: function (toggle) {
            kendo.ui.progress(this.wrapper, toggle);
        },

        _resize: function () {
            var columnTable = this.columnsHeader.children("table");

            if (this.content[0].firstChild) {
                this._setSectionsWidth();
                this._setSectionsHeight();
                this._setContentWidth();
                this._setContentHeight();

                columnTable.css("table-layout", AUTO);

                clearTimeout(this._layoutTimeout);
                this._layoutTimeout = setTimeout(function () {
                    columnTable.css("table-layout", "fixed");
                });
            }
        },

        _setSectionsWidth: function () {
            var rowsHeader = this.rowsHeader;
            var leftColumn = rowsHeader.parent(".k-pivot-rowheaders").width(AUTO);
            var width;

            width = Math.max(this.measureFields.outerWidth(), this.rowFields.outerWidth());
            width = Math.max(rowsHeader.children("table").width(), width);

            leftColumn.width(width);
        },

        _setSectionsHeight: function () {
            var measureFieldsHeight = this.measureFields.height(AUTO).height();
            var columnFieldsHeight = this.columnFields.height(AUTO).height();
            var rowFieldsHeight = this.rowFields.height(AUTO).innerHeight();
            var columnsHeight = this.columnsHeader.height(AUTO).innerHeight();

            var padding = rowFieldsHeight - this.rowFields.height();

            var firstRowHeight = columnFieldsHeight > measureFieldsHeight ? columnFieldsHeight : measureFieldsHeight;
            var secondRowHeight = columnsHeight > rowFieldsHeight ? columnsHeight : rowFieldsHeight;

            this.measureFields.height(firstRowHeight);
            this.columnFields.height(firstRowHeight);
            this.rowFields.height(secondRowHeight - padding);
            this.columnsHeader.height(secondRowHeight);
        },

        _setContentWidth: function () {
            var contentTable = this.content.find("table");
            var contentWidth = this.content.width();

            var rowLength = contentTable.children("colgroup").children().length;

            var minWidth = 100;
            var calculatedWidth = rowLength * this.options.columnWidth;

            if (contentWidth < calculatedWidth) {
                minWidth = Math.ceil((calculatedWidth / contentWidth) * 100);
            }

            contentTable.add(this.columnsHeader.children("table"))
                        .css("min-width", minWidth + "%");

        },

        _setContentHeight: function () {
            var that = this;
            var content = that.content;
            var rowsHeader = that.rowsHeader;
            var height = that.wrapper.innerHeight();
            var scrollbar = kendo.support.scrollbar();
            var skipScrollbar = content[0].offsetHeight === content[0].clientHeight;

            if (that.wrapper.is(":visible")) {
                if (!height) {
                    if (skipScrollbar) {
                        scrollbar = 0;
                    }

                    rowsHeader.height(content.height() - scrollbar);
                    return;
                }

                height -= that.columnFields.outerHeight();
                height -= that.columnsHeader.outerHeight();

                if (height <= scrollbar * 2) { // do not set height if proper scrollbar cannot be displayed
                    height = scrollbar * 2 + 1;
                    if (!skipScrollbar) {
                        height += scrollbar;
                    }
                }

                content.height(height);

                if (skipScrollbar) {
                    scrollbar = 0;
                }

                rowsHeader.height(height - scrollbar);
            }
        },

        _axisMeasures: function (axis) {
            var result = [];
            var dataSource = this.dataSource;
            var measures = dataSource.measures();
            var hasMeasure = measures.length > 1 || (measures[0] && measures[0].type);

            if (dataSource.measuresAxis() === axis) {
                if (dataSource[axis]().length === 0 || hasMeasure) {
                    result = measures;
                }
            }

            return result;
        },

        refresh: function () {
            var that = this;
            var dataSource = that.dataSource;

            var axes = dataSource.axes();
            var columns = (axes.columns || {}).tuples || [];
            var rows = (axes.rows || {}).tuples || [];

            var columnBuilder = that._columnBuilder;
            var rowBuilder = that._rowBuilder;

            var columnAxis = {};
            var rowAxis = {};

            if (that.trigger(DATABINDING, { action: "rebind" })) {
                return;
            }

            columnBuilder.measures = this._axisMeasures("columns");

            that.columnsHeaderTree.render(columnBuilder.build(columns));
            that.rowsHeaderTree.render(rowBuilder.build(rows));

            columnAxis = {
                indexes: columnBuilder._indexes,
                measures: columnBuilder.measures,
                metadata: columnBuilder.metadata
            };

            rowAxis = {
                indexes: rowBuilder._indexes,
                measures: this._axisMeasures("rows"),
                metadata: rowBuilder.metadata
            };

            that.contentTree.render(that._contentBuilder.build(dataSource.view(), columnAxis, rowAxis));

            that._resize();

            if (that.touchScroller) {
                that.touchScroller.contentResized();
            } else {
                var touchScroller = kendo.touchScroller(that.content);

                if (touchScroller && touchScroller.movable) {
                    that.touchScroller = touchScroller;

                    touchScroller.movable.bind("change", function (e) {
                        that.columnsHeader.scrollLeft(-e.sender.x);
                        that.rowsHeader.scrollTop(-e.sender.y);
                    });
                }
            }

            that._progress(false);

            that.trigger(DATABOUND);
        },

        _scrollable: function () {
            var that = this;
            var columnsHeader = that.columnsHeader;
            var rowsHeader = that.rowsHeader;

            that.content.scroll(function () {
                columnsHeader.scrollLeft(this.scrollLeft);
                rowsHeader.scrollTop(this.scrollTop);
            });

            rowsHeader.bind("DOMMouseScroll" + NS + " mousewheel" + NS, $.proxy(that._wheelScroll, that));
        },

        _wheelScroll: function (e) {
            if (e.ctrlKey) {
                return;
            }

            var delta = kendo.wheelDeltaY(e);
            var scrollTop = this.content.scrollTop();

            if (delta) {
                e.preventDefault();
                //In Firefox DOMMouseScroll event cannot be canceled
                $(e.currentTarget).one("wheel" + NS, false);

                this.rowsHeader.scrollTop(scrollTop + (-delta));
                this.content.scrollTop(scrollTop + (-delta));
            }
        }
    });

    var element = kendo.dom.element;
    var htmlNode = kendo.dom.html;
    var text = kendo.dom.text;

    var createMetadata = function (levelNum, memberIdx) {
        return {
            maxChildren: 0,
            children: 0,
            maxMembers: 0,
            members: 0,
            measures: 1,
            levelNum: levelNum,
            parentMember: memberIdx !== 0
        };
    };

    var buildPath = function (tuple, index) {
        var path = [];
        var idx = 0;

        for (; idx <= index; idx++) {
            path.push(tuple.members[idx].name);
        }

        return path;
    };

    var tupleName = function (tuple, index) {
        var name = "";
        var idx = 0;

        for (; idx <= index; idx++) {
            name += tuple.members[idx].name;
        }

        return name;
    };

    var ColumnBuilder = Class.extend({
        init: function (options) {
            this.measures = 1;
            this.metadata = {};
        },

        build: function (tuples) {
            var tbody = this._tbody(tuples);
            var colgroup = this._colGroup();

            return [
                element("table", null, [colgroup, tbody])
            ];
        },

        reset: function () {
            this.metadata = {};
        },

        _colGroup: function () {
            var length = this._rowLength();
            var children = [];
            var idx = 0;

            for (; idx < length; idx++) {
                children.push(element("col", null));
            }

            return element("colgroup", null, children);
        },

        _tbody: function (tuples) {
            var root = tuples[0];

            this.map = {};
            this.rows = [];
            this.rootTuple = root;

            this._indexes = [];

            if (root) {
                this._buildRows(root, 0);
                this._normalize();
            } else {
                this.rows.push(element("tr", null, [element("th", null, [htmlNode("&nbsp;")])]));
            }

            return element("tbody", null, this.rows);
        },

        _normalize: function () {
            var rows = this.rows;
            var rowsLength = rows.length;
            var rowIdx = 0;
            var row;

            var cellsLength;
            var cellIdx;
            var cells;
            var cell;

            for (; rowIdx < rowsLength; rowIdx++) {
                row = rows[rowIdx];

                if (row.rowSpan === 1) {
                    continue;
                }

                cells = row.children;

                cellIdx = 0;
                cellsLength = cells.length;

                for (; cellIdx < cellsLength; cellIdx++) {
                    cell = cells[cellIdx];

                    if (cell.tupleAll) {
                        cell.attr.rowSpan = row.rowSpan;
                    }
                }
            }
        },

        _rowIndex: function (row) {
            var rows = this.rows;
            var length = rows.length;
            var idx = 0;

            for (; idx < length; idx++) {
                if (rows[idx] === row) {
                    break;
                }
            }

            return idx;
        },

        _rowLength: function () {
            var cells = this.rows[0] ? this.rows[0].children : [];
            var length = cells.length;
            var rowLength = 0;
            var idx = 0;

            if (length) {
                for (; idx < length; idx++) {
                    rowLength += cells[idx].attr.colSpan || 1;
                }
            }

            if (!rowLength) {
                rowLength = this.measures;
            }

            return rowLength;
        },

        _row: function (tuple, memberIdx, parentMember) {
            var rootName = this.rootTuple.members[memberIdx].name;
            var levelNum = tuple.members[memberIdx].levelNum;
            var rowKey = rootName + levelNum;
            var map = this.map;
            var parentRow;
            var children;

            var row = map[rowKey];

            if (!row) {
                row = element("tr", null, []);

                row.parentMember = parentMember;
                row.colSpan = 0;
                row.rowSpan = 1;

                map[rowKey] = row;
                parentRow = map[rootName + (Number(levelNum) - 1)];

                if (parentRow) {
                    children = parentRow.children;

                    if (children[1] && children[1].attr.className.indexOf("k-alt") === -1) {
                        row.notFirst = true;
                    } else {
                        row.notFirst = parentRow.notFirst;
                    }
                }

                this.rows.splice(this._rowIndex(parentRow) + 1, 0, row);
            } else {
                row.notFirst = false;

                if (!row.parentMember || row.parentMember !== parentMember) {
                    row.parentMember = parentMember;
                    row.colSpan = 0;
                }
            }

            return row;
        },

        _measures: function (measures, tuple, className) {
            var map = this.map;
            var row = map.measureRow;
            var measure;

            if (!row) {
                row = element("tr", null, []);
                map.measureRow = row;
                this.rows.push(row);
            }

            for (var idx = 0, length = measures.length; idx < length; idx++) {
                measure = measures[idx];
                row.children.push(element("th", { className: "k-header" + (className || "") }, [this._content(measure, tuple)]));
            }

            return length;
        },

        _content: function (member, tuple) {
            return htmlNode(this.template({
                member: member,
                tuple: tuple
            }));
        },

        _cell: function (className, children) {
            return element("th", { className: "k-header" + className }, children);
        },

        _buildRows: function (tuple, memberIdx, parentMember) {
            var members = tuple.members;
            var member = members[memberIdx];
            var nextMember = members[memberIdx + 1];

            var row, childRow, children, childrenLength;
            var cell, allCell, cellAttr;
            var cellChildren = [];
            var path;

            var idx = 0;
            var colSpan;
            var metadata;

            if (member.measure) {
                this._measures(member.children, tuple);
                return;
            }

            path = kendo.stringify(buildPath(tuple, memberIdx));
            row = this._row(tuple, memberIdx, parentMember);

            children = member.children;
            childrenLength = children.length;

            metadata = this.metadata[path];
            if (!metadata) {
                this.metadata[path] = metadata = createMetadata(Number(member.levelNum), memberIdx);
            }

            this._indexes.push({
                path: path,
                tuple: tuple
            });

            if (member.hasChildren) {
                if (metadata.expanded === false) {
                    childrenLength = 0;
                    metadata.children = 0;
                }

                cellAttr = { className: "k-icon " + (childrenLength ? STATE_EXPANDED : STATE_COLLAPSED) };
                cellAttr[kendo.attr("path")] = path;

                cellChildren.push(element("span", cellAttr));
            }

            cellChildren.push(this._content(member, tuple));
            cell = this._cell((row.notFirst ? " k-first" : ""), cellChildren);

            row.children.push(cell);
            row.colSpan += 1;

            if (childrenLength) {
                allCell = this._cell(" k-alt", [this._content(member, tuple)]);
                row.children.push(allCell);

                for (; idx < childrenLength; idx++) {
                    childRow = this._buildRows(children[idx], memberIdx, member);
                }

                colSpan = childRow.colSpan;
                cell.attr.colSpan = colSpan;

                metadata.children = colSpan;
                metadata.members = 1;

                row.colSpan += colSpan;
                row.rowSpan = childRow.rowSpan + 1;

                if (nextMember) {
                    if (nextMember.measure) {
                        colSpan = this._measures(nextMember.children, tuple, " k-alt");
                    } else {
                        colSpan = this._buildRows(tuple, memberIdx + 1).colSpan;
                    }

                    allCell.attr.colSpan = colSpan;
                    colSpan -= 1;

                    metadata.members += colSpan;
                    row.colSpan += colSpan;
                }
            } else if (nextMember) {
                if (nextMember.measure) {
                    colSpan = this._measures(nextMember.children, tuple);
                } else {
                    colSpan = this._buildRows(tuple, memberIdx + 1).colSpan;
                }

                metadata.members = colSpan;

                if (colSpan > 1) {
                    cell.attr.colSpan = colSpan;
                    row.colSpan += colSpan - 1;
                }
            }

            if (metadata.maxChildren < metadata.children) {
                metadata.maxChildren = metadata.children;
            }

            if (metadata.maxMembers < metadata.members) {
                metadata.maxMembers = metadata.members;
            }

            (allCell || cell).tupleAll = true;

            return row;
        }
    });

    var RowBuilder = Class.extend({
        init: function (options) {
            this.metadata = {};
        },

        build: function (tuples) {
            var tbody = this._tbody(tuples);
            var colgroup = this._colGroup();

            return [
                element("table", null, [colgroup, tbody])
            ];
        },

        reset: function () {
            this.metadata = {};
        },

        _colGroup: function () {
            var length = this.rows[0].children.length;
            var children = [];
            var idx = 0;

            for (; idx < length; idx++) {
                children.push(element("col", null));
            }

            return element("colgroup", null, children);
        },

        _tbody: function (tuples) {
            var root = tuples[0];

            this.rootTuple = root;
            this.rows = [];
            this.map = {};

            this._indexes = [];

            if (root) {
                this._buildRows(root, 0);
                this._normalize();
            } else {
                this.rows.push(element("tr", null, [element("td", null, [htmlNode("&nbsp;")])]));
            }

            return element("tbody", null, this.rows);
        },

        _normalize: function () {
            var rows = this.rows;
            var rowsLength = rows.length;
            var rowIdx = 0;

            var members = this.rootTuple.members;
            var firstMemberName = members[0].name;
            var membersLength = members.length;
            var memberIdx = 0;

            var row;
            var cell;
            var maxcolSpan;
            var map = this.map;
            var allRow;

            for (; rowIdx < rowsLength; rowIdx++) {
                row = rows[rowIdx];

                for (memberIdx = 0; memberIdx < membersLength; memberIdx++) {
                    maxcolSpan = this[members[memberIdx].name];
                    cell = row.colSpan["dim" + memberIdx];

                    if (cell && cell.levelNum < maxcolSpan) {
                        cell.attr.colSpan = (maxcolSpan - cell.levelNum) + 1;
                    }
                }
            }

            row = map[firstMemberName];
            allRow = map[firstMemberName + "all"];

            if (row) {
                row.children[0].attr.className = "k-first";
            }

            if (allRow) {
                allRow.children[0].attr.className += " k-first";
            }
        },

        _row: function (children) {
            var row = element("tr", null, children);
            row.rowSpan = 1;
            row.colSpan = {};

            this.rows.push(row);

            return row;
        },

        _content: function (member, tuple) {
            return htmlNode(this.template({
                member: member,
                tuple: tuple
            }));
        },

        _buildRows: function (tuple, memberIdx) {
            var map = this.map;
            var path;

            var members = tuple.members;
            var member = members[memberIdx];
            var nextMember = members[memberIdx + 1];

            var children = member.children;
            var childrenLength = children.length;

            var levelNum = Number(member.levelNum) + 1;
            var rootName = this.rootTuple.members[memberIdx].name;
            var tuplePath = buildPath(tuple, memberIdx - 1).join("");

            var parentName = tuplePath + (member.parentName || "");
            var row = map[parentName + "all"] || map[parentName];
            var childRow;
            var allRow;

            var metadata;
            var expandIconAttr;
            var cellChildren = [];
            var allCell;
            var cell;
            var attr;
            var idx;

            if (!row || row.hasChild) {
                row = this._row();
            } else {
                row.hasChild = true;
            }

            if (member.measure) {
                attr = { className: row.allCell ? "k-grid-footer" : "" };
                row.children.push(element("td", attr, [this._content(children[0], tuple)]));

                row.rowSpan = childrenLength;

                for (idx = 1; idx < childrenLength; idx++) {
                    this._row([element("td", attr, [this._content(children[idx], tuple)])]);
                }

                return row;
            }

            map[tuplePath + member.name] = row;

            path = kendo.stringify(buildPath(tuple, memberIdx));

            metadata = this.metadata[path];
            if (!metadata) {
                this.metadata[path] = metadata = createMetadata(levelNum - 1, memberIdx);
            }

            this._indexes.push({
                path: path,
                tuple: tuple
            });

            if (member.hasChildren) {
                if (metadata.expanded === false) {
                    childrenLength = 0;
                    metadata.children = 0;
                }

                expandIconAttr = { className: "k-icon " + (childrenLength ? STATE_EXPANDED : STATE_COLLAPSED) };
                expandIconAttr[kendo.attr("path")] = path;

                cellChildren.push(element("span", expandIconAttr));
            }

            cellChildren.push(this._content(member, tuple));
            cell = element("td", { className: row.allCell && !childrenLength ? "k-grid-footer" : "" }, cellChildren);
            cell.levelNum = levelNum;

            row.children.push(cell);
            row.colSpan["dim" + memberIdx] = cell;

            if (!this[rootName] || this[rootName] < levelNum) {
                this[rootName] = levelNum;
            }

            if (childrenLength) {
                row.allCell = false;
                row.hasChild = false;

                for (idx = 0; idx < childrenLength; idx++) {
                    childRow = this._buildRows(children[idx], memberIdx);

                    if (row !== childRow) {
                        row.rowSpan += childRow.rowSpan;
                    }
                }

                if (row.rowSpan > 1) {
                    cell.attr.rowSpan = row.rowSpan;
                }

                metadata.children = row.rowSpan;

                allCell = element("td", { className: "k-grid-footer" }, [this._content(member, tuple)]);
                allCell.levelNum = levelNum;

                allRow = this._row([allCell]);
                allRow.colSpan["dim" + memberIdx] = allCell;
                allRow.allCell = true;

                map[tuplePath + member.name + "all"] = allRow;

                if (nextMember) {
                    childRow = this._buildRows(tuple, memberIdx + 1);
                    allCell.attr.rowSpan = childRow.rowSpan;
                }

                row.rowSpan += allRow.rowSpan;

                metadata.members = allRow.rowSpan;

            } else if (nextMember) {
                row.hasChild = false;
                this._buildRows(tuple, memberIdx + 1);

                (allCell || cell).attr.rowSpan = row.rowSpan;

                metadata.members = row.rowSpan;
            }

            if (metadata.maxChildren < metadata.children) {
                metadata.maxChildren = metadata.children;
            }

            if (metadata.maxMembers < metadata.members) {
                metadata.maxMembers = metadata.members;
            }

            return row;
        }
    });

    var ContentBuilder = Class.extend({
        init: function () {
            this.columnAxis = {};
            this.rowAxis = {};
        },

        build: function (data, columnAxis, rowAxis) {
            var index = columnAxis.indexes[0];
            var metadata = columnAxis.metadata[index ? index.path : undefined];

            this.columnAxis = columnAxis;
            this.rowAxis = rowAxis;

            this.data = data;

            this.rowLength = metadata ? metadata.maxChildren + metadata.maxMembers : columnAxis.measures.length || 1;

            if (!this.rowLength) {
                this.rowLength = 1;
            }

            var tbody = this._tbody();
            var colgroup = this._colGroup();

            return [
                element("table", null, [colgroup, tbody])
            ];
        },

        _colGroup: function () {
            var length = this.columnAxis.measures.length || 1;
            var children = [];
            var idx = 0;

            if (this.rows[0]) {
                length = this.rows[0].children.length;
            }

            for (; idx < length; idx++) {
                children.push(element("col", null));
            }

            return element("colgroup", null, children);
        },

        _tbody: function () {
            this.rows = [];

            if (this.data[0]) {
                this.columnIndexes = this._indexes(this.columnAxis);
                this.rowIndexes = this._indexes(this.rowAxis);

                this._buildRows();
            } else {
                this.rows.push(element("tr", null, [element("td", null, [htmlNode("&nbsp;")])]));
            }

            return element("tbody", null, this.rows);
        },

        _indexes: function (axisInfo) {
            var result = [];
            var axisInfoMember;
            var indexes = axisInfo.indexes;
            var metadata = axisInfo.metadata;
            var measures = axisInfo.measures;
            var measuresLength = measures.length || 1;

            var current;
            var dataIdx = 0;
            var firstEmpty = 0;

            var idx = 0;
            var length = indexes.length;
            var measureIdx;

            var children;
            var skipChildren;

            if (!length) {
                for (measureIdx = 0; measureIdx < measuresLength; measureIdx++) {
                    result[measureIdx] = {
                        index: measureIdx,
                        measure: measures[measureIdx],
                        tuple: null
                    };
                }

                return result;
            }

            for (; idx < length; idx++) {
                axisInfoMember = indexes[idx];
                current = metadata[axisInfoMember.path];
                children = current.children + current.members;
                skipChildren = 0;

                if (children) {
                    children -= measuresLength;
                }

                if (current.expanded === false && current.children !== current.maxChildren) {
                    skipChildren = current.maxChildren;
                }

                if (current.parentMember && current.levelNum === 0) {
                    children = -1;
                }

                if (children > -1) {
                    for (measureIdx = 0; measureIdx < measuresLength; measureIdx++) {
                        result[children + firstEmpty + measureIdx] = {
                            children: children,
                            index: dataIdx,
                            measure: measures[measureIdx],
                            tuple: axisInfoMember.tuple
                        };
                        dataIdx += 1;
                    }

                    while (result[firstEmpty] !== undefined) {
                        firstEmpty += 1;
                    }
                }

                dataIdx += skipChildren;
            }

            return result;
        },

        _buildRows: function () {
            var rowIndexes = this.rowIndexes;
            var length = rowIndexes.length;
            var idx = 0;

            for (; idx < length; idx++) {
                this.rows.push(this._buildRow(rowIndexes[idx]));
            }
        },

        _buildRow: function (rowInfo) {
            var startIdx = rowInfo.index * this.rowLength;
            var columnIndexes = this.columnIndexes;
            var length = columnIndexes.length;
            var columnInfo;
            var cells = [];
            var idx = 0;

            var templateInfo;
            var cellContent;
            var attr, dataItem, measure;

            for (; idx < length; idx++) {
                columnInfo = columnIndexes[idx];

                attr = {};
                if (columnInfo.children) {
                    attr.className = "k-alt";
                }

                cellContent = "";
                dataItem = this.data[startIdx + columnInfo.index];
                measure = columnInfo.measure || rowInfo.measure;

                templateInfo = {
                    columnTuple: columnInfo.tuple,
                    rowTuple: rowInfo.tuple,
                    measure: measure,
                    dataItem: dataItem
                };

                if (dataItem.value !== "" && measure && measure.type) {
                    if (measure.type === "status") {
                        cellContent = this.kpiStatusTemplate(templateInfo);
                    } else if (measure.type === "trend") {
                        cellContent = this.kpiTrendTemplate(templateInfo);
                    }
                }

                if (!cellContent) {
                    cellContent = this.dataTemplate(templateInfo);
                }

                cells.push(element("td", attr, [htmlNode(cellContent)]));
            }

            attr = {};
            if (rowInfo.children) {
                attr.className = "k-grid-footer";
            }

            return element("tr", attr, cells);
        }
    });

    ui.plugin(PivotGrid);
})(window.kendo.jQuery);





/*jshint eqnull: true */
(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        data = kendo.data,
        extend = $.extend,
        template = kendo.template,
        isArray = $.isArray,
        Widget = ui.Widget,
        HierarchicalDataSource = data.HierarchicalDataSource,
        proxy = $.proxy,
        keys = kendo.keys,
        NS = ".kendoTreeView",
        SELECT = "select",
        CHECK = "check",
        NAVIGATE = "navigate",
        EXPAND = "expand",
        CHANGE = "change",
        ERROR = "error",
        CHECKED = "checked",
        INDETERMINATE = "indeterminate",
        COLLAPSE = "collapse",
        DRAGSTART = "dragstart",
        DRAG = "drag",
        DROP = "drop",
        DRAGEND = "dragend",
        DATABOUND = "dataBound",
        CLICK = "click",
        VISIBILITY = "visibility",
        UNDEFINED = "undefined",
        KSTATEHOVER = "k-state-hover",
        KTREEVIEW = "k-treeview",
        VISIBLE = ":visible",
        NODE = ".k-item",
        STRING = "string",
        ARIASELECTED = "aria-selected",
        ARIADISABLED = "aria-disabled",
        TreeView,
        subGroup, nodeContents, nodeIcon,
        spriteRe,
        bindings = {
            text: "dataTextField",
            url: "dataUrlField",
            spriteCssClass: "dataSpriteCssClassField",
            imageUrl: "dataImageUrlField"
        },
        isDomElement = function (o) {
            return (
                typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
                o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName === STRING
            );
        };

    function contentChild(filter) {
        return function (node) {
            var result = node.children(".k-animation-container");

            if (!result.length) {
                result = node;
            }

            return result.children(filter);
        };
    }

    function templateNoWith(code) {
        return kendo.template(code, { useWithBlock: false });
    }

    subGroup = contentChild(".k-group");
    nodeContents = contentChild(".k-group,.k-content");
    nodeIcon = function (node) {
        return node.children("div").children(".k-icon");
    };

    function checkboxes(node) {
        return node.find("> div .k-checkbox [type=checkbox]");
    }

    function insertAction(indexOffset) {
        return function (nodeData, referenceNode) {
            referenceNode = referenceNode.closest(NODE);

            var group = referenceNode.parent(),
                parentNode;

            if (group.parent().is("li")) {
                parentNode = group.parent();
            }

            return this._dataSourceMove(nodeData, group, parentNode, function (dataSource, model) {
                return this._insert(dataSource.data(), model, referenceNode.index() + indexOffset);
            });
        };
    }

    spriteRe = /k-sprite/;

    function moveContents(node, container) {
        var tmp;

        while (node && node.nodeName.toLowerCase() != "ul") {
            tmp = node;
            node = node.nextSibling;

            if (tmp.nodeType == 3) {
                tmp.nodeValue = $.trim(tmp.nodeValue);
            }

            if (spriteRe.test(tmp.className)) {
                container.insertBefore(tmp, container.firstChild);
            } else {
                container.appendChild(tmp);
            }
        }
    }

    function updateNodeHtml(node) {
        var wrapper = node.children("div"),
            group = node.children("ul"),
            toggleButton = wrapper.children(".k-icon"),
            checkbox = node.children(":checkbox"),
            innerWrapper = wrapper.children(".k-in");

        if (node.hasClass("k-treeview")) {
            return;
        }

        if (!wrapper.length) {
            wrapper = $("<div />").prependTo(node);
        }

        if (!toggleButton.length && group.length) {
            toggleButton = $("<span class='k-icon' />").prependTo(wrapper);
        } else if (!group.length || !group.children().length) {
            toggleButton.remove();
            group.remove();
        }

        if (checkbox.length) {
            $("<span class='k-checkbox' />").appendTo(wrapper).append(checkbox);
        }

        if (!innerWrapper.length) {
            innerWrapper = node.children("a").eq(0).addClass("k-in");

            if (!innerWrapper.length) {
                innerWrapper = $("<span class='k-in' />");
            }

            innerWrapper.appendTo(wrapper);

            if (wrapper.length) {
                moveContents(wrapper[0].nextSibling, innerWrapper[0]);
            }
        }
    }

    TreeView = kendo.ui.DataBoundWidget.extend({
        init: function (element, options) {
            var that = this,
                dataInit,
                inferred = false,
                hasDataSource = options && !!options.dataSource,
                list;

            if (isArray(options)) {
                dataInit = true;
                options = { dataSource: options };
            }

            if (options && typeof options.loadOnDemand == UNDEFINED && isArray(options.dataSource)) {
                options.loadOnDemand = false;
            }

            Widget.prototype.init.call(that, element, options);

            element = that.element;
            options = that.options;

            list = (element.is("ul") && element) ||
                   (element.hasClass(KTREEVIEW) && element.children("ul"));

            inferred = !hasDataSource && list.length;

            if (inferred) {
                options.dataSource.list = list;
            }

            that._animation();

            that._accessors();

            that._templates();

            // render treeview if it's not already rendered
            if (!element.hasClass(KTREEVIEW)) {
                that._wrapper();

                if (list) {
                    that.root = element;
                    that._group(that.wrapper);
                }
            } else {
                // otherwise just initialize properties
                that.wrapper = element;
                that.root = element.children("ul").eq(0);
            }

            that._tabindex();

            that.root.attr("role", "tree");

            that._dataSource(inferred);

            that._attachEvents();

            that._dragging();

            if (!inferred) {
                if (options.autoBind) {
                    that._progress(true);
                    that.dataSource.fetch();
                }
            } else {
                that._syncHtmlAndDataSource();
            }

            if (options.checkboxes && options.checkboxes.checkChildren) {
                that.updateIndeterminate();
            }

            if (that.element[0].id) {
                that._ariaId = kendo.format("{0}_tv_active", that.element[0].id);
            }

            kendo.notify(that);
        },

        _attachEvents: function () {
            var that = this,
                clickableItems = ".k-in:not(.k-state-selected,.k-state-disabled)",
                MOUSEENTER = "mouseenter";

            that.wrapper
                .on(MOUSEENTER + NS, ".k-in.k-state-selected", function (e) { e.preventDefault(); })
                .on(MOUSEENTER + NS, clickableItems, function () { $(this).addClass(KSTATEHOVER); })
                .on("mouseleave" + NS, clickableItems, function () { $(this).removeClass(KSTATEHOVER); })
                .on(CLICK + NS, clickableItems, proxy(that._click, that))
                .on("dblclick" + NS, ".k-in:not(.k-state-disabled)", proxy(that._toggleButtonClick, that))
                .on(CLICK + NS, ".k-plus,.k-minus", proxy(that._toggleButtonClick, that))
                .on("keydown" + NS, proxy(that._keydown, that))
                .on("focus" + NS, proxy(that._focus, that))
                .on("blur" + NS, proxy(that._blur, that))
                .on("mousedown" + NS, ".k-in,.k-checkbox :checkbox,.k-plus,.k-minus", proxy(that._mousedown, that))
                .on("change" + NS, ".k-checkbox :checkbox", proxy(that._checkboxChange, that))
                .on("click" + NS, ".k-checkbox :checkbox", proxy(that._checkboxClick, that))
                .on("click" + NS, ".k-request-retry", proxy(that._retryRequest, that))
                .on("click" + NS, function (e) {
                    if (!$(e.target).is(":kendoFocusable")) {
                        that.focus();
                    }
                });
        },

        _checkboxClick: function (e) {
            var checkbox = $(e.target);

            if (checkbox.data(INDETERMINATE)) {
                checkbox
                    .data(INDETERMINATE, false)
                    .prop(INDETERMINATE, false)
                    .prop(CHECKED, true);

                this._checkboxChange(e);
            }
        },

        _syncHtmlAndDataSource: function (root, dataSource) {
            root = root || this.root;
            dataSource = dataSource || this.dataSource;

            var data = dataSource.view();
            var uidAttr = kendo.attr("uid");
            var expandedAttr = kendo.attr("expanded");
            var inferCheckedState = this.options.checkboxes;
            var items = root.children("li");
            var i, item, dataItem;

            for (i = 0; i < items.length; i++) {
                dataItem = data[i];

                item = items.eq(i);

                item.attr("role", "treeitem").attr(uidAttr, dataItem.uid);

                dataItem.expanded = item.attr(expandedAttr) === "true";
                if (inferCheckedState) {
                    dataItem.checked = checkboxes(item).prop(CHECKED);
                }

                this._syncHtmlAndDataSource(item.children("ul"), dataItem.children);
            }
        },

        _animation: function () {
            var options = this.options,
                animationOptions = options.animation;

            if (animationOptions === false) {
                animationOptions = {
                    expand: { effects: {} },
                    collapse: { hide: true, effects: {} }
                };
            } else if (!animationOptions.collapse || !("effects" in animationOptions.collapse)) {
                animationOptions.collapse = extend({ reverse: true }, animationOptions.expand);
            }

            extend(animationOptions.collapse, { hide: true });

            options.animation = animationOptions;
        },

        _dragging: function () {
            var enabled = this.options.dragAndDrop;
            var dragging = this.dragging;

            if (enabled && !dragging) {
                this.dragging = new TreeViewDragAndDrop(this);
            } else if (!enabled && dragging) {
                dragging.destroy();
                this.dragging = null;
            }
        },

        _templates: function () {
            var that = this,
                options = that.options,
                fieldAccessor = proxy(that._fieldAccessor, that);

            if (options.template && typeof options.template == STRING) {
                options.template = template(options.template);
            } else if (!options.template) {
                options.template = templateNoWith(
                    "# var text = " + fieldAccessor("text") + "(data.item); #" +
                    "# if (typeof data.item.encoded != 'undefined' && data.item.encoded === false) {#" +
                        "#= text #" +
                    "# } else { #" +
                        "#: text #" +
                    "# } #"
                );
            }

            that._checkboxes();

            that.templates = {
                wrapperCssClass: function (group, item) {
                    var result = "k-item",
                        index = item.index;

                    if (group.firstLevel && index === 0) {
                        result += " k-first";
                    }

                    if (index == group.length - 1) {
                        result += " k-last";
                    }

                    return result;
                },
                cssClass: function (group, item) {
                    var result = "",
                        index = item.index,
                        groupLength = group.length - 1;

                    if (group.firstLevel && index === 0) {
                        result += "k-top ";
                    }

                    if (index === 0 && index != groupLength) {
                        result += "k-top";
                    } else if (index == groupLength) {
                        result += "k-bot";
                    } else {
                        result += "k-mid";
                    }

                    return result;
                },
                textClass: function (item) {
                    var result = "k-in";

                    if (item.enabled === false) {
                        result += " k-state-disabled";
                    }

                    if (item.selected === true) {
                        result += " k-state-selected";
                    }

                    return result;
                },
                toggleButtonClass: function (item) {
                    var result = "k-icon";

                    if (item.expanded !== true) {
                        result += " k-plus";
                    } else {
                        result += " k-minus";
                    }

                    if (item.enabled === false) {
                        result += "-disabled";
                    }

                    return result;
                },
                groupAttributes: function (group) {
                    var attributes = "";

                    if (!group.firstLevel) {
                        attributes = "role='group'";
                    }

                    return attributes + (group.expanded !== true ? " style='display:none'" : "");
                },
                groupCssClass: function (group) {
                    var cssClass = "k-group";

                    if (group.firstLevel) {
                        cssClass += " k-treeview-lines";
                    }

                    return cssClass;
                },
                dragClue: templateNoWith(
                    "<div class='k-header k-drag-clue'>" +
                        "<span class='k-icon k-drag-status' />" +
                        "#= data.treeview.template(data) #" +
                    "</div>"
                ),
                group: templateNoWith(
                    "<ul class='#= data.r.groupCssClass(data.group) #'#= data.r.groupAttributes(data.group) #>" +
                        "#= data.renderItems(data) #" +
                    "</ul>"
                ),
                itemContent: templateNoWith(
                    "# var imageUrl = " + fieldAccessor("imageUrl") + "(data.item); #" +
                    "# var spriteCssClass = " + fieldAccessor("spriteCssClass") + "(data.item); #" +
                    "# if (imageUrl) { #" +
                        "<img class='k-image' alt='' src='#= imageUrl #'>" +
                    "# } #" +

                    "# if (spriteCssClass) { #" +
                        "<span class='k-sprite #= spriteCssClass #' />" +
                    "# } #" +

                    "#= data.treeview.template(data) #"
                ),
                itemElement: templateNoWith(
                    "# var item = data.item, r = data.r; #" +
                    "# var url = " + fieldAccessor("url") + "(item); #" +
                    "<div class='#= r.cssClass(data.group, item) #'>" +
                        "# if (item.hasChildren) { #" +
                            "<span class='#= r.toggleButtonClass(item) #' role='presentation' />" +
                        "# } #" +

                        "# if (data.treeview.checkboxes) { #" +
                            "<span class='k-checkbox' role='presentation'>" +
                                "#= data.treeview.checkboxes.template(data) #" +
                            "</span>" +
                        "# } #" +

                        "# var tag = url ? 'a' : 'span'; #" +
                        "# var textAttr = url ? ' href=\\'' + url + '\\'' : ''; #" +

                        "<#=tag#  class='#= r.textClass(item) #'#= textAttr #>" +
                            "#= r.itemContent(data) #" +
                        "</#=tag#>" +
                    "</div>"
                ),
                item: templateNoWith(
                    "# var item = data.item, r = data.r; #" +
                    "<li role='treeitem' class='#= r.wrapperCssClass(data.group, item) #' " +
                        kendo.attr("uid") + "='#= item.uid #' " +
                        "aria-selected='#= item.selected ? \"true\" : \"false \" #' " +
                        "#=item.enabled === false ? \"aria-disabled='true'\" : ''#" +
                    ">" +
                        "#= r.itemElement(data) #" +
                    "</li>"
                ),
                loading: templateNoWith(
                    "<div class='k-icon k-loading' /> #: data.messages.loading #"
                ),
                retry: templateNoWith(
                    "#: data.messages.requestFailed # " +
                    "<button class='k-button k-request-retry'>#: data.messages.retry #</button>"
                )
            };
        },

        items: function () {
            return this.element.find(".k-item div:first-child");
        },

        setDataSource: function (dataSource) {
            var options = this.options;

            options.dataSource = dataSource;

            this._dataSource();

            this.dataSource.fetch();

            if (options.checkboxes && options.checkboxes.checkChildren) {
                this.updateIndeterminate();
            }
        },

        _bindDataSource: function () {
            this._refreshHandler = proxy(this.refresh, this);
            this._errorHandler = proxy(this._error, this);

            this.dataSource.bind(CHANGE, this._refreshHandler);
            this.dataSource.bind(ERROR, this._errorHandler);
        },

        _unbindDataSource: function () {
            var dataSource = this.dataSource;

            if (dataSource) {
                dataSource.unbind(CHANGE, this._refreshHandler);
                dataSource.unbind(ERROR, this._errorHandler);
            }
        },

        _dataSource: function (silentRead) {
            var that = this,
                options = that.options,
                dataSource = options.dataSource;

            function recursiveRead(data) {
                for (var i = 0; i < data.length; i++) {
                    data[i]._initChildren();

                    data[i].children.fetch();

                    recursiveRead(data[i].children.view());
                }
            }

            dataSource = isArray(dataSource) ? { data: dataSource} : dataSource;

            that._unbindDataSource();

            if (!dataSource.fields) {
                dataSource.fields = [
                    { field: "text" },
                    { field: "url" },
                    { field: "spriteCssClass" },
                    { field: "imageUrl" }
                ];
            }

            that.dataSource = dataSource = HierarchicalDataSource.create(dataSource);

            if (silentRead) {
                dataSource.fetch();

                recursiveRead(dataSource.view());
            }

            that._bindDataSource();
        },

        events: [
            DRAGSTART,
            DRAG,
            DROP,
            DRAGEND,

            DATABOUND,

            EXPAND,
            COLLAPSE,
            SELECT,
            CHANGE,
            NAVIGATE,
            CHECK
        ],

        options: {
            name: "TreeView",
            dataSource: {},
            animation: {
                expand: {
                    effects: "expand:vertical",
                    duration: 200
                }, collapse: {
                    duration: 100
                }
            },
            messages: {
                loading: "Loading...",
                requestFailed: "Request failed.",
                retry: "Retry"
            },
            dragAndDrop: false,
            checkboxes: false,
            autoBind: true,
            loadOnDemand: true,
            template: "",
            dataTextField: null
        },

        _accessors: function () {
            var that = this,
                options = that.options,
                i, field, textField,
                element = that.element;

            for (i in bindings) {
                field = options[bindings[i]];
                textField = element.attr(kendo.attr(i + "-field"));

                if (!field && textField) {
                    field = textField;
                }

                if (!field) {
                    field = i;
                }

                if (!isArray(field)) {
                    field = [field];
                }

                options[bindings[i]] = field;
            }
        },

        // generates accessor function for a given field name, honoring the data*Field arrays
        _fieldAccessor: function (fieldName) {
            var fieldBindings = this.options[bindings[fieldName]],
                count = fieldBindings.length,
                result = "(function(item) {";

            if (count === 0) {
                result += "return item['" + fieldName + "'];";
            } else {
                result += "var levels = [" +
                            $.map(fieldBindings, function (x) {
                                return "function(d){ return " + kendo.expr(x) + "}";
                            }).join(",") + "];";

                result += "return levels[Math.min(item.level(), " + count + "-1)](item)";
            }

            result += "})";

            return result;
        },

        setOptions: function (options) {
            Widget.fn.setOptions.call(this, options);

            this._animation();

            this._dragging();

            this._templates();
        },

        _trigger: function (eventName, node) {
            return this.trigger(eventName, {
                node: node.closest(NODE)[0]
            });
        },

        _setChecked: function (datasource, value) {
            if (!datasource || !$.isFunction(datasource.view)) {
                return;
            }

            for (var i = 0, nodes = datasource.view(); i < nodes.length; i++) {
                nodes[i][CHECKED] = value;

                if (nodes[i].children) {
                    this._setChecked(nodes[i].children, value);
                }
            }
        },

        _setIndeterminate: function (node) {
            var group = subGroup(node),
                siblings, length,
                all = true,
                i;

            if (!group.length) {
                return;
            }

            siblings = checkboxes(group.children());
            length = siblings.length;

            if (!length) {
                return;
            } else if (length > 1) {
                for (i = 1; i < length; i++) {
                    if (siblings[i].checked != siblings[i - 1].checked ||
                        siblings[i].indeterminate || siblings[i - 1].indeterminate) {
                        all = false;
                        break;
                    }
                }
            } else {
                all = !siblings[0].indeterminate;
            }

            return checkboxes(node)
                .data(INDETERMINATE, !all)
                .prop(INDETERMINATE, !all)
                .prop(CHECKED, all && siblings[0].checked);
        },

        updateIndeterminate: function (node) {
            // top-down update of inital indeterminate state for all nodes
            node = node || this.wrapper;

            var subnodes = subGroup(node).children();
            var i;
            var checkbox;

            if (subnodes.length) {
                for (i = 0; i < subnodes.length; i++) {
                    this.updateIndeterminate(subnodes.eq(i));
                }

                checkbox = this._setIndeterminate(node);

                if (checkbox && checkbox.prop(CHECKED)) {
                    this.dataItem(node).checked = true;
                }
            }
        },

        _bubbleIndeterminate: function (node) {
            // bottom-up setting of indeterminate state of parent nodes
            if (!node.length) {
                return;
            }

            var parentNode = this.parent(node),
                checkbox;

            if (parentNode.length) {
                this._setIndeterminate(parentNode);
                checkbox = parentNode.children("div").find(".k-checkbox :checkbox");

                if (checkbox.prop(INDETERMINATE) === false) {
                    this.dataItem(parentNode).set(CHECKED, checkbox.prop(CHECKED));
                } else {
                    this.dataItem(parentNode).checked = false;
                }

                this._bubbleIndeterminate(parentNode);
            }
        },

        _checkboxChange: function (e) {
            var checkbox = $(e.target);
            var isChecked = checkbox.prop(CHECKED);
            var node = checkbox.closest(NODE);

            this.dataItem(node).set(CHECKED, isChecked);

            this._trigger(CHECK, node);
        },

        _toggleButtonClick: function (e) {
            this.toggle($(e.target).closest(NODE));
        },

        _mousedown: function (e) {
            var node = $(e.currentTarget).closest(NODE);

            this._clickTarget = node;
            this.current(node);
        },

        _focusable: function (node) {
            return node && node.length && node.is(":visible") && !node.find(".k-in:first").hasClass("k-state-disabled");
        },

        _focus: function () {
            var current = this.select(),
                clickTarget = this._clickTarget;

            // suppress initial focus state on touch devices (until keyboard is used)
            if (kendo.support.touch) {
                return;
            }

            if (clickTarget && clickTarget.length) {
                current = clickTarget;
            }

            if (!this._focusable(current)) {
                current = this.current();
            }

            if (!this._focusable(current)) {
                current = this._nextVisible($());
            }

            this.current(current);
        },

        focus: function () {
            var wrapper = this.wrapper,
                scrollContainer = wrapper[0],
                containers = [],
                offsets = [],
                documentElement = document.documentElement,
                i;

            do {
                scrollContainer = scrollContainer.parentNode;

                if (scrollContainer.scrollHeight > scrollContainer.clientHeight) {
                    containers.push(scrollContainer);
                    offsets.push(scrollContainer.scrollTop);
                }
            } while (scrollContainer != documentElement);

            wrapper.focus();

            for (i = 0; i < containers.length; i++) {
                containers[i].scrollTop = offsets[i];
            }
        },

        _blur: function () {
            this.current().find(".k-in:first").removeClass("k-state-focused");
        },

        _enabled: function (node) {
            return !node.children("div").children(".k-in").hasClass("k-state-disabled");
        },

        parent: function (node) {
            var wrapperRe = /\bk-treeview\b/,
                itemRe = /\bk-item\b/,
                result,
                skipSelf;

            if (typeof node == STRING) {
                node = this.element.find(node);
            }

            if (!isDomElement(node)) {
                node = node[0];
            }

            skipSelf = itemRe.test(node.className);

            do {
                node = node.parentNode;

                if (itemRe.test(node.className)) {
                    if (skipSelf) {
                        result = node;
                    } else {
                        skipSelf = true;
                    }
                }
            } while (!wrapperRe.test(node.className) && !result);

            return $(result);
        },

        _nextVisible: function (node) {
            var that = this,
                expanded = that._expanded(node),
                result;

            function nextParent(node) {
                while (node.length && !node.next().length) {
                    node = that.parent(node);
                }

                if (node.next().length) {
                    return node.next();
                } else {
                    return node;
                }
            }

            if (!node.length || !node.is(":visible")) {
                result = that.root.children().eq(0);
            } else if (expanded) {
                result = subGroup(node).children().first();

                // expanded node with no children
                if (!result.length) {
                    result = nextParent(node);
                }
            } else {
                result = nextParent(node);
            }

            if (!that._enabled(result)) {
                result = that._nextVisible(result);
            }

            return result;
        },

        _previousVisible: function (node) {
            var that = this,
                lastChild,
                result;

            if (!node.length || node.prev().length) {
                if (node.length) {
                    result = node.prev();
                } else {
                    result = that.root.children().last();
                }

                while (that._expanded(result)) {
                    lastChild = subGroup(result).children().last();

                    if (!lastChild.length) {
                        break;
                    }

                    result = lastChild;
                }
            } else {
                result = that.parent(node) || node;
            }

            if (!that._enabled(result)) {
                result = that._previousVisible(result);
            }

            return result;
        },

        _keydown: function (e) {
            var that = this,
                key = e.keyCode,
                target,
                focused = that.current(),
                expanded = that._expanded(focused),
                checkbox = focused.find(".k-checkbox:first :checkbox"),
                rtl = kendo.support.isRtl(that.element);

            if (e.target != e.currentTarget) {
                return;
            }

            if ((!rtl && key == keys.RIGHT) || (rtl && key == keys.LEFT)) {
                if (expanded) {
                    target = that._nextVisible(focused);
                } else {
                    that.expand(focused);
                }
            } else if ((!rtl && key == keys.LEFT) || (rtl && key == keys.RIGHT)) {
                if (expanded) {
                    that.collapse(focused);
                } else {
                    target = that.parent(focused);

                    if (!that._enabled(target)) {
                        target = undefined;
                    }
                }
            } else if (key == keys.DOWN) {
                target = that._nextVisible(focused);
            } else if (key == keys.UP) {
                target = that._previousVisible(focused);
            } else if (key == keys.HOME) {
                target = that._nextVisible($());
            } else if (key == keys.END) {
                target = that._previousVisible($());
            } else if (key == keys.ENTER) {
                if (!focused.find(".k-in:first").hasClass("k-state-selected")) {
                    if (!that._trigger(SELECT, focused)) {
                        that.select(focused);
                    }
                }
            } else if (key == keys.SPACEBAR && checkbox.length) {
                checkbox.prop(CHECKED, !checkbox.prop(CHECKED))
                    .data(INDETERMINATE, false)
                    .prop(INDETERMINATE, false);

                that._checkboxChange({ target: checkbox });

                target = focused;
            }

            if (target) {
                e.preventDefault();

                if (focused[0] != target[0]) {
                    that._trigger(NAVIGATE, target);
                    that.current(target);
                }
            }
        },

        _click: function (e) {
            var that = this,
                node = $(e.currentTarget),
                contents = nodeContents(node.closest(NODE)),
                href = node.attr("href"),
                shouldNavigate;

            if (href) {
                shouldNavigate = href == "#" || href.indexOf("#" + this.element.id + "-") >= 0;
            } else {
                shouldNavigate = contents.length && !contents.children().length;
            }

            if (shouldNavigate) {
                e.preventDefault();
            }

            if (!node.hasClass(".k-state-selected") && !that._trigger(SELECT, node)) {
                that.select(node);
            }
        },

        _wrapper: function () {
            var that = this,
                element = that.element,
                wrapper, root,
                wrapperClasses = "k-widget k-treeview";

            if (element.is("ul")) {
                wrapper = element.wrap('<div />').parent();
                root = element;
            } else {
                wrapper = element;
                root = wrapper.children("ul").eq(0);
            }

            that.wrapper = wrapper.addClass(wrapperClasses);
            that.root = root;
        },

        _group: function (item) {
            var that = this,
                firstLevel = item.hasClass(KTREEVIEW),
                group = {
                    firstLevel: firstLevel,
                    expanded: firstLevel || that._expanded(item)
                },
                groupElement = item.children("ul");

            groupElement
                .addClass(that.templates.groupCssClass(group))
                .css("display", group.expanded ? "" : "none");

            that._nodes(groupElement, group);
        },

        _nodes: function (groupElement, groupData) {
            var that = this,
                nodes = groupElement.children("li"),
                nodeData;

            groupData = extend({ length: nodes.length }, groupData);

            nodes.each(function (i, node) {
                node = $(node);

                nodeData = { index: i, expanded: that._expanded(node) };

                updateNodeHtml(node);

                that._updateNodeClasses(node, groupData, nodeData);

                // iterate over child nodes
                that._group(node);
            });
        },

        _checkboxes: function () {
            var options = this.options;
            var checkboxes = options.checkboxes;
            var defaultTemplate;

            if (checkboxes) {
                defaultTemplate = "<input type='checkbox' #= (item.enabled === false) ? 'disabled' : '' # #= item.checked ? 'checked' : '' #";

                if (checkboxes.name) {
                    defaultTemplate += " name='" + checkboxes.name + "'";
                }

                defaultTemplate += " />";

                checkboxes = extend({
                    template: defaultTemplate
                }, options.checkboxes);

                if (typeof checkboxes.template == STRING) {
                    checkboxes.template = template(checkboxes.template);
                }

                options.checkboxes = checkboxes;
            }
        },

        _updateNodeClasses: function (node, groupData, nodeData) {
            var wrapper = node.children("div"),
                group = node.children("ul"),
                templates = this.templates;

            if (node.hasClass("k-treeview")) {
                return;
            }

            nodeData = nodeData || {};
            nodeData.expanded = typeof nodeData.expanded != UNDEFINED ? nodeData.expanded : this._expanded(node);
            nodeData.index = typeof nodeData.index != UNDEFINED ? nodeData.index : node.index();
            nodeData.enabled = typeof nodeData.enabled != UNDEFINED ? nodeData.enabled : !wrapper.children(".k-in").hasClass("k-state-disabled");

            groupData = groupData || {};
            groupData.firstLevel = typeof groupData.firstLevel != UNDEFINED ? groupData.firstLevel : node.parent().parent().hasClass(KTREEVIEW);
            groupData.length = typeof groupData.length != UNDEFINED ? groupData.length : node.parent().children().length;

            // li
            node.removeClass("k-first k-last")
                .addClass(templates.wrapperCssClass(groupData, nodeData));

            // div
            wrapper.removeClass("k-top k-mid k-bot")
                   .addClass(templates.cssClass(groupData, nodeData));

            // span
            wrapper.children(".k-in").removeClass("k-in k-state-default k-state-disabled")
                .addClass(templates.textClass(nodeData));

            // toggle button
            if (group.length || node.attr("data-hasChildren") == "true") {
                wrapper.children(".k-icon").removeClass("k-plus k-minus k-plus-disabled k-minus-disabled")
                    .addClass(templates.toggleButtonClass(nodeData));

                group.addClass("k-group");
            }
        },


        _processNodes: function (nodes, callback) {
            var that = this;
            that.element.find(nodes).each(function (index, item) {
                callback.call(that, index, $(item).closest(NODE));
            });
        },

        dataItem: function (node) {
            var uid = $(node).closest(NODE).attr(kendo.attr("uid")),
                dataSource = this.dataSource;

            return dataSource && dataSource.getByUid(uid);
        },

        _insertNode: function (nodeData, index, parentNode, insertCallback, collapsed) {
            var that = this,
                group = subGroup(parentNode),
                updatedGroupLength = group.children().length + 1,
                childrenData,
                groupData = {
                    firstLevel: parentNode.hasClass(KTREEVIEW),
                    expanded: !collapsed,
                    length: updatedGroupLength
                }, node, i, item, nodeHtml = "",
                append = function (item, group) {
                    item.appendTo(group);
                };

            for (i = 0; i < nodeData.length; i++) {
                item = nodeData[i];

                item.index = index + i;

                nodeHtml += that._renderItem({
                    group: groupData,
                    item: item
                });
            }

            node = $(nodeHtml);

            if (!node.length) {
                return;
            }

            that.angular("compile", function () {
                return {
                    elements: node.get(),
                    data: nodeData.map(function (item) {
                        return { dataItem: item };
                    })
                };
            });

            if (!group.length) {
                group = $(that._renderGroup({
                    group: groupData
                })).appendTo(parentNode);
            }

            insertCallback(node, group);

            if (parentNode.hasClass("k-item")) {
                updateNodeHtml(parentNode);
                that._updateNodeClasses(parentNode);
            }

            that._updateNodeClasses(node.prev().first());
            that._updateNodeClasses(node.next().last());

            // render sub-nodes
            for (i = 0; i < nodeData.length; i++) {
                item = nodeData[i];

                if (item.hasChildren) {
                    childrenData = item.children.data();

                    if (childrenData.length) {
                        that._insertNode(childrenData, item.index, node.eq(i), append, !that._expanded(node.eq(i)));
                    }
                }
            }

            return node;
        },

        _updateNode: function (field, items) {
            var that = this, i, node, item,
                isChecked, isCollapsed,
                context = { treeview: that.options, item: item },
                shouldUpdate = false;

            function access() {
                shouldUpdate = true;
            }

            function setCheckedState(root, state) {
                root.find(".k-checkbox :checkbox")
                    .prop(CHECKED, state)
                    .data(INDETERMINATE, false)
                    .prop(INDETERMINATE, false);
            }

            if (field == "selected") {
                item = items[0];

                node = that.findByUid(item.uid).find(".k-in:first")
                        .removeClass("k-state-hover")
                        .toggleClass("k-state-selected", item[field])
                        .end();

                if (item[field]) {
                    that.current(node);
                    node.attr(ARIASELECTED, true);
                } else {
                    node.attr(ARIASELECTED, false);
                }
            } else {
                if ($.inArray(field, that.options.dataTextField) >= 0) {
                    shouldUpdate = true;
                } else {
                    context.item = items[0];
                    context.item.bind("get", access);
                    that.templates.itemContent(context);
                    context.item.unbind("set", access);
                }

                for (i = 0; i < items.length; i++) {
                    context.item = item = items[i];

                    if (field == "spriteCssClass" || field == "imageUrl" || shouldUpdate) {
                        that.findByUid(item.uid).find(">div>.k-in").html(that.templates.itemContent(context));
                    }

                    if (field == CHECKED) {
                        node = that.findByUid(item.uid);

                        isChecked = item[field];

                        setCheckedState(node.children("div"), isChecked);

                        if (that.options.checkboxes.checkChildren) {
                            setCheckedState(node.children(".k-group"), isChecked);

                            that._setChecked(item.children, isChecked);

                            that._bubbleIndeterminate(node);
                        }
                    } else if (field == "expanded") {
                        that._toggle(that.findByUid(item.uid), item, item[field]);
                    } else if (field == "enabled") {
                        node = that.findByUid(item.uid);

                        node.find(".k-checkbox :checkbox").prop("disabled", !item[field]);

                        isCollapsed = !nodeContents(node).is(VISIBLE);

                        node.removeAttr(ARIADISABLED);

                        if (!item[field]) {
                            if (item.selected) {
                                item.set("selected", false);
                            }

                            if (item.expanded) {
                                item.set("expanded", false);
                            }

                            isCollapsed = true;
                            node.attr(ARIASELECTED, false)
                                .attr(ARIADISABLED, true);
                        }

                        that._updateNodeClasses(node, {}, { enabled: item[field], expanded: !isCollapsed });
                    }
                }
            }
        },

        _appendItems: function (index, items, parentNode) {
            var group = subGroup(parentNode);
            var children = group.children();
            var collapsed = !this._expanded(parentNode);

            if (typeof index == UNDEFINED) {
                index = children.length;
            }

            this._insertNode(items, index, parentNode, function (item, group) {
                // insert node into DOM
                if (index >= children.length) {
                    item.appendTo(group);
                } else {
                    item.insertBefore(children.eq(index));
                }
            }, collapsed);

            if (this._expanded(parentNode)) {
                this._updateNodeClasses(parentNode);
                subGroup(parentNode).css("display", "block");
            }
        },

        refresh: function (e) {
            var parentNode = this.wrapper,
                node = e.node,
                action = e.action,
                items = e.items,
                options = this.options,
                loadOnDemand = options.loadOnDemand,
                checkChildren = options.checkboxes && options.checkboxes.checkChildren,
                i;

            if (e.field) {
                if (!items[0] || !items[0].level) {
                    return;
                }

                return this._updateNode(e.field, items);
            }

            if (node) {
                parentNode = this.findByUid(node.uid);
                this._progress(parentNode, false);
            }

            if (checkChildren && action != "remove") {
                var bubble = false;

                for (i = 0; i < items.length; i++) {
                    if ("checked" in items[i]) {
                        bubble = true;
                        break;
                    }
                }

                if (!bubble && node && node.checked) {
                    for (i = 0; i < items.length; i++) {
                        items[i].checked = true;
                    }
                }
            }

            if (action == "add") {
                this._appendItems(e.index, items, parentNode);
            } else if (action == "remove") {
                this._remove(this.findByUid(items[0].uid), false);
            } else {
                if (node) {
                    subGroup(parentNode).empty();

                    if (!items.length) {
                        updateNodeHtml(parentNode);

                        this.trigger("itemChange", { item: parentNode, data: node, ns: ui });
                    } else {
                        this._appendItems(e.index, items, parentNode);

                        var children = subGroup(parentNode).children();

                        if (loadOnDemand && checkChildren) {
                            this._bubbleIndeterminate(children.last());
                        }

                        for (i = 0; i < children.length; i++) {
                            var child = children.eq(i);
                            this.trigger("itemChange", { item: child, data: this.dataItem(child), ns: ui });
                        }
                    }
                } else {

                    var groupHtml = this._renderGroup({
                        items: items,
                        group: {
                            firstLevel: true,
                            expanded: true
                        }
                    });

                    if (this.root.length) {

                        this._angularItems("cleanup");

                        var group = $(groupHtml);

                        this.root
                            .attr("class", group.attr("class"))
                            .html(group.html());
                    } else {
                        this.root = this.wrapper.html(groupHtml).children("ul");
                    }

                    this.root.attr("role", "tree");

                    this._angularItems("compile");
                }
            }

            for (i = 0; i < items.length; i++) {
                if (!loadOnDemand || items[i].expanded) {
                    items[i].load();
                }
            }

            this.trigger(DATABOUND, {
                node: node ? parentNode : undefined
            });
        },

        _error: function (e) {
            var node = e.node && this.findByUid(e.node.uid);
            var retryHtml = this.templates.retry({ messages: this.options.messages });

            if (node) {
                this._progress(node, false);
                this._expanded(node, false);
                nodeIcon(node).addClass("k-i-refresh");
                e.node.loaded(false);
            } else {
                this._progress(false);
                this.element.html(retryHtml);
            }
        },

        _retryRequest: function (e) {
            e.preventDefault();

            this.dataSource.fetch();
        },

        expand: function (nodes) {
            this._processNodes(nodes, function (index, item) {
                this.toggle(item, true);
            });
        },

        collapse: function (nodes) {
            this._processNodes(nodes, function (index, item) {
                this.toggle(item, false);
            });
        },

        enable: function (nodes, enable) {
            enable = arguments.length == 2 ? !!enable : true;

            this._processNodes(nodes, function (index, item) {
                this.dataItem(item).set("enabled", enable);
            });
        },

        current: function (node) {
            var that = this,
                current = that._current,
                element = that.element,
                id = that._ariaId;

            if (arguments.length > 0 && node && node.length) {
                if (current) {
                    if (current[0].id === id) {
                        current.removeAttr("id");
                    }

                    current.find(".k-in:first").removeClass("k-state-focused");
                }

                current = that._current = $(node, element).closest(NODE);

                current.find(".k-in:first").addClass("k-state-focused");

                id = current[0].id || id;

                if (id) {
                    that.wrapper.removeAttr("aria-activedescendant");
                    current.attr("id", id);
                    that.wrapper.attr("aria-activedescendant", id);
                }

                return;
            }

            if (!current) {
                current = that._nextVisible($());
            }

            return current;
        },

        select: function (node) {
            var that = this,
                element = that.element;

            if (!arguments.length) {
                return element.find(".k-state-selected").closest(NODE);
            }

            node = $(node, element).closest(NODE);

            element.find(".k-state-selected").each(function () {
                var dataItem = that.dataItem(this);
                dataItem.set("selected", false);
                delete dataItem.selected;
            });

            if (node.length) {
                that.dataItem(node).set("selected", true);
            }

            that.trigger(CHANGE);
        },

        _toggle: function (node, dataItem, expand) {
            var options = this.options;
            var contents = nodeContents(node);
            var direction = expand ? "expand" : "collapse";
            var loaded, empty;

            if (contents.data("animating")) {
                return;
            }

            if (!this._trigger(direction, node)) {
                this._expanded(node, expand);

                loaded = dataItem && dataItem.loaded();
                empty = !contents.children().length;

                if (expand && (!loaded || empty)) {
                    if (options.loadOnDemand) {
                        this._progress(node, true);
                    }

                    contents.remove();
                    dataItem.load();
                } else {
                    this._updateNodeClasses(node, {}, { expanded: expand });

                    if (contents.css("display") == (expand ? "block" : "none")) {
                        return;
                    }

                    if (!expand) {
                        contents.css("height", contents.height()).css("height");
                    }

                    contents
                        .kendoStop(true, true)
                        .kendoAnimate(extend(
                            { reset: true },
                            options.animation[direction],
                            { complete: function () {
                                if (expand) {
                                    contents.css("height", "");
                                }
                            } 
                            }
                        ));
                }
            }
        },

        toggle: function (node, expand) {
            node = $(node);

            if (!nodeIcon(node).is(".k-minus,.k-plus,.k-minus-disabled,.k-plus-disabled")) {
                return;
            }

            if (arguments.length == 1) {
                expand = !this._expanded(node);
            }

            this._expanded(node, expand);
        },

        destroy: function () {
            var that = this;

            Widget.fn.destroy.call(that);

            that.wrapper.off(NS);

            that._unbindDataSource();

            if (that.dragging) {
                that.dragging.destroy();
            }

            kendo.destroy(that.element);

            that.root = that.wrapper = that.element = null;
        },

        _expanded: function (node, value) {
            var expandedAttr = kendo.attr("expanded"),
                dataItem = this.dataItem(node);

            if (arguments.length == 1) {
                return node.attr(expandedAttr) === "true" || (dataItem && dataItem.expanded);
            }

            if (nodeContents(node).data("animating")) {
                return;
            }

            if (dataItem) {
                dataItem.set("expanded", value);
                // necessary when expanding an item yields an error and the item is not expanded as a result
                value = dataItem.expanded;
            }

            if (value) {
                node.attr(expandedAttr, "true");
                node.attr("aria-expanded", "true");
            } else {
                node.removeAttr(expandedAttr);
                node.attr("aria-expanded", "false");
            }
        },

        _progress: function (node, showProgress) {
            var element = this.element;
            var loadingText = this.templates.loading({ messages: this.options.messages });

            if (arguments.length == 1) {
                showProgress = node;

                if (showProgress) {
                    element.html(loadingText);
                } else {
                    element.empty();
                }
            } else {
                nodeIcon(node).toggleClass("k-loading", showProgress).removeClass("k-i-refresh");
            }
        },

        text: function (node, text) {
            var dataItem = this.dataItem(node),
                fieldBindings = this.options[bindings.text],
                level = dataItem.level(),
                length = fieldBindings.length,
                field = fieldBindings[Math.min(level, length - 1)];

            if (text) {
                dataItem.set(field, text);
            } else {
                return dataItem[field];
            }
        },

        _objectOrSelf: function (node) {
            return $(node).closest("[data-role=treeview]").data("kendoTreeView") || this;
        },

        _dataSourceMove: function (nodeData, group, parentNode, callback) {
            var referenceDataItem,
                destTreeview = this._objectOrSelf(parentNode || group),
                destDataSource = destTreeview.dataSource;

            if (parentNode && parentNode[0] != destTreeview.element[0]) {
                referenceDataItem = destTreeview.dataItem(parentNode);

                if (!referenceDataItem.loaded()) {
                    destTreeview._progress(parentNode, true);
                    referenceDataItem.load();
                }

                if (parentNode != this.root) {
                    destDataSource = referenceDataItem.children;

                    if (!destDataSource || !(destDataSource instanceof HierarchicalDataSource)) {
                        referenceDataItem._initChildren();
                        referenceDataItem.loaded(true);
                        destDataSource = referenceDataItem.children;
                    }
                }
            }

            nodeData = this._toObservableData(nodeData);

            return callback.call(this, destDataSource, nodeData);
        },

        _toObservableData: function (node) {
            var dataItem = node, dataSource, uid;

            if (node instanceof window.jQuery || isDomElement(node)) {
                dataSource = this._objectOrSelf(node).dataSource;
                uid = $(node).attr(kendo.attr("uid"));
                dataItem = dataSource.getByUid(uid);

                if (dataItem) {
                    dataItem = dataSource.remove(dataItem);
                }
            }

            return dataItem;
        },

        _insert: function (data, model, index) {
            if (!(model instanceof kendo.data.ObservableArray)) {
                if (!isArray(model)) {
                    model = [model];
                }
            } else {
                // items will be converted to new Node instances
                model = model.toJSON();
            }

            var parentNode = data.parent();

            if (parentNode && parentNode._initChildren) {
                parentNode.hasChildren = true;
                parentNode._initChildren();
            }

            data.splice.apply(data, [index, 0].concat(model));

            return this.findByUid(data[index].uid);
        },

        insertAfter: insertAction(1),

        insertBefore: insertAction(0),

        append: function (nodeData, parentNode, success) {
            var that = this,
                group = that.root;

            success = success || $.noop;

            if (parentNode) {
                group = subGroup(parentNode);
            }

            return that._dataSourceMove(nodeData, group, parentNode, function (dataSource, model) {
                var inserted;

                function add() {
                    if (parentNode) {
                        that._expanded(parentNode, true);
                    }

                    var data = dataSource.data(),
                        index = Math.max(data.length, 0);

                    return that._insert(data, model, index);
                }

                if (!dataSource.data()) {
                    dataSource.one(CHANGE, function () {
                        success(add());
                    });

                    return null;
                } else {
                    inserted = add();
                    success(inserted);
                    return inserted;
                }
            });
        },

        _remove: function (node, keepData) {
            var that = this,
                parentNode,
                prevSibling, nextSibling;

            node = $(node, that.element);

            this.angular("cleanup", function () {
                return { elements: node.get() };
            });

            parentNode = node.parent().parent();
            prevSibling = node.prev();
            nextSibling = node.next();

            node[keepData ? "detach" : "remove"]();

            if (parentNode.hasClass("k-item")) {
                updateNodeHtml(parentNode);
                that._updateNodeClasses(parentNode);
            }

            that._updateNodeClasses(prevSibling);
            that._updateNodeClasses(nextSibling);

            return node;
        },

        remove: function (node) {
            var dataItem = this.dataItem(node);
            if (dataItem) {
                this.dataSource.remove(dataItem);
            }
        },

        detach: function (node) {
            return this._remove(node, true);
        },

        findByText: function (text) {
            return $(this.element).find(".k-in").filter(function (i, element) {
                return $(element).text() == text;
            }).closest(NODE);
        },

        findByUid: function (uid) {
            return this.element.find(".k-item[" + kendo.attr("uid") + "=" + uid + "]");
        },

        expandPath: function (path, complete) {
            path = path.slice(0);
            var treeview = this;
            var dataSource = this.dataSource;
            var node = dataSource.get(path[0]);
            complete = complete || $.noop;

            // expand loaded nodes
            while (path.length > 0 && (node.expanded || node.loaded())) {
                node.set("expanded", true);
                path.shift();
                node = dataSource.get(path[0]);
            }

            if (!path.length) {
                return complete.call(treeview);
            }

            // expand async nodes
            dataSource.bind("change", function expandLevel(e) {
                // listen to the change event to know when the node has been loaded
                var id = e.node && e.node.id;

                // proceed if the change is caused by the last fetching
                if (id && id === path[0]) {
                    path.shift();

                    if (path.length) {
                        dataSource.get(path[0]).set("expanded", true);
                    } else {
                        complete.call(treeview);
                    }
                }
            });

            node.set("expanded", true);
        },

        _parents: function (node) {
            var parent = node && node.parentNode();
            var parents = [];
            while (parent) {
                parents.push(parent);
                parent = parent.parentNode();
            }

            return parents;
        },

        expandTo: function (node) {
            if (!(node instanceof kendo.data.Node)) {
                node = this.dataSource.get(node);
            }

            var parents = this._parents(node);

            for (var i = 0; i < parents.length; i++) {
                parents[i].set("expanded", true);
            }
        },

        _renderItem: function (options) {
            if (!options.group) {
                options.group = {};
            }

            options.treeview = this.options;

            options.r = this.templates;

            return this.templates.item(options);
        },

        _renderGroup: function (options) {
            var that = this;

            options.renderItems = function (options) {
                var html = "",
                        i = 0,
                        items = options.items,
                        len = items ? items.length : 0,
                        group = options.group;

                group.length = len;

                for (; i < len; i++) {
                    options.group = group;
                    options.item = items[i];
                    options.item.index = i;
                    html += that._renderItem(options);
                }

                return html;
            };

            options.r = that.templates;

            return that.templates.group(options);
        }
    });

    function TreeViewDragAndDrop(treeview) {
        var that = this;

        that.treeview = treeview;
        that.hovered = treeview.element;

        that._draggable = new ui.Draggable(treeview.element, {
            filter: "div:not(.k-state-disabled) .k-in",
            hint: function (node) {
                return treeview.templates.dragClue({
                    item: treeview.dataItem(node),
                    treeview: treeview.options
                });
            },
            cursorOffset: {
                left: 10,
                top: kendo.support.mobileOS ? -40 / kendo.support.zoomLevel() : 10
            },
            dragstart: proxy(that.dragstart, that),
            dragcancel: proxy(that.dragcancel, that),
            drag: proxy(that.drag, that),
            dragend: proxy(that.dragend, that),
            $angular: treeview.options.$angular
        });
    }

    TreeViewDragAndDrop.prototype = {
        _removeTouchHover: function () {
            var that = this;

            if (kendo.support.touch && that.hovered) {
                that.hovered.find("." + KSTATEHOVER).removeClass(KSTATEHOVER);
                that.hovered = false;
            }
        },

        _hintStatus: function (newStatus) {
            var statusElement = this._draggable.hint.find(".k-drag-status")[0];

            if (newStatus) {
                statusElement.className = "k-icon k-drag-status " + newStatus;
            } else {
                return $.trim(statusElement.className.replace(/k-(icon|drag-status)/g, ""));
            }
        },

        dragstart: function (e) {
            var that = this,
                treeview = that.treeview,
                sourceNode = that.sourceNode = e.currentTarget.closest(NODE);

            if (treeview.trigger(DRAGSTART, { sourceNode: sourceNode[0] })) {
                e.preventDefault();
            }

            that.dropHint = $("<div class='k-drop-hint' />")
                .css(VISIBILITY, "hidden")
                .appendTo(treeview.element);
        },

        drag: function (e) {
            var that = this,
                treeview = that.treeview,
                sourceNode = that.sourceNode,
                dropTarget = that.dropTarget = $(kendo.eventTarget(e)),
                statusClass, closestTree = dropTarget.closest(".k-treeview"),
                hoveredItem, hoveredItemPos, itemHeight, itemTop, itemContent, delta,
                insertOnTop, insertOnBottom, addChild;

            if (!closestTree.length) {
                // dragging node outside of treeview
                statusClass = "k-denied";
                that._removeTouchHover();
            } else if ($.contains(sourceNode[0], dropTarget[0])) {
                // dragging node within itself
                statusClass = "k-denied";
            } else {
                // moving or reordering node
                statusClass = "k-insert-middle";

                hoveredItem = dropTarget.closest(".k-top,.k-mid,.k-bot");

                if (hoveredItem.length) {
                    itemHeight = hoveredItem.outerHeight();
                    itemTop = kendo.getOffset(hoveredItem).top;
                    itemContent = dropTarget.closest(".k-in");
                    delta = itemHeight / (itemContent.length > 0 ? 4 : 2);

                    insertOnTop = e.y.location < (itemTop + delta);
                    insertOnBottom = (itemTop + itemHeight - delta) < e.y.location;
                    that._removeTouchHover();
                    addChild = itemContent.length && !insertOnTop && !insertOnBottom;
                    that.hovered = addChild ? closestTree : false;

                    that.dropHint.css(VISIBILITY, addChild ? "hidden" : "visible");
                    itemContent.toggleClass(KSTATEHOVER, addChild);

                    if (addChild) {
                        statusClass = "k-add";
                    } else {
                        hoveredItemPos = hoveredItem.position();
                        hoveredItemPos.top += insertOnTop ? 0 : itemHeight;

                        that.dropHint
                            .css(hoveredItemPos)
                            [insertOnTop ? "prependTo" : "appendTo"](dropTarget.closest(NODE).children("div:first"));

                        if (insertOnTop && hoveredItem.hasClass("k-top")) {
                            statusClass = "k-insert-top";
                        }

                        if (insertOnBottom && hoveredItem.hasClass("k-bot")) {
                            statusClass = "k-insert-bottom";
                        }
                    }
                } else if (dropTarget[0] != that.dropHint[0]) {
                    if (closestTree[0] != treeview.element[0]) {
                        // moving node to different treeview without children
                        statusClass = "k-add";
                    } else {
                        statusClass = "k-denied";
                    }
                }
            }

            treeview.trigger(DRAG, {
                sourceNode: sourceNode[0],
                dropTarget: dropTarget[0],
                pageY: e.y.location,
                pageX: e.x.location,
                statusClass: statusClass.substring(2),
                setStatusClass: function (value) {
                    statusClass = value;
                }
            });

            if (statusClass.indexOf("k-insert") !== 0) {
                that.dropHint.css(VISIBILITY, "hidden");
            }

            that._hintStatus(statusClass);
        },

        dragcancel: function () {
            this.dropHint.remove();
        },

        dragend: function () {
            var that = this,
                treeview = that.treeview,
                dropPosition = "over",
                sourceNode = that.sourceNode,
                destinationNode,
                dropHint = that.dropHint,
                dropTarget = that.dropTarget,
                e, dropPrevented;

            if (dropHint.css(VISIBILITY) == "visible") {
                dropPosition = dropHint.prevAll(".k-in").length > 0 ? "after" : "before";
                destinationNode = dropHint.closest(NODE);
            } else if (dropTarget) {
                destinationNode = dropTarget.closest(".k-treeview .k-item");

                // moving node to root element
                if (!destinationNode.length) {
                    destinationNode = dropTarget.closest(".k-treeview");
                }
            }

            e = {
                sourceNode: sourceNode[0],
                destinationNode: destinationNode[0],
                valid: that._hintStatus() != "k-denied",
                setValid: function (newValid) {
                    this.valid = newValid;
                },
                dropTarget: dropTarget[0],
                dropPosition: dropPosition
            };

            dropPrevented = treeview.trigger(DROP, e);

            dropHint.remove();
            that._removeTouchHover();

            if (!e.valid || dropPrevented) {
                that._draggable.dropped = e.valid;
                return;
            }

            that._draggable.dropped = true;

            function triggerDragEnd(sourceNode) {
                treeview.updateIndeterminate();

                treeview.trigger(DRAGEND, {
                    sourceNode: sourceNode && sourceNode[0],
                    destinationNode: destinationNode[0],
                    dropPosition: dropPosition
                });
            }

            // perform reorder / move
            // different handling is necessary because append might be async in remote bound tree
            if (dropPosition == "over") {
                treeview.append(sourceNode, destinationNode, triggerDragEnd);
            } else {
                if (dropPosition == "before") {
                    sourceNode = treeview.insertBefore(sourceNode, destinationNode);
                } else if (dropPosition == "after") {
                    sourceNode = treeview.insertAfter(sourceNode, destinationNode);
                }

                triggerDragEnd(sourceNode);
            }
        },

        destroy: function () {
            this._draggable.destroy();
        }
    };

    ui.plugin(TreeView);
})(window.kendo.jQuery);





/*jshint eqnull: true*/
(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        MENU = "kendoContextMenu",
        proxy = $.proxy,
        NS = ".kendoPivotFieldMenu",
        Widget = ui.Widget;

    var PivotFieldMenu = Widget.extend({
        init: function (element, options) {
            Widget.fn.init.call(this, element, options);

            this._dataSource();

            this._layout();

            kendo.notify(this);
        },

        events: [],

        options: {
            name: "PivotFieldMenu",
            filter: null,
            filterable: true,
            sortable: true,
            messages: {
                info: "Show items with value that:",
                sortAscending: "Sort Ascending",
                sortDescending: "Sort Descending",
                filterFields: "Fields Filter",
                filter: "Filter",
                include: "Include Fields...",
                title: "Fields to include",
                clear: "Clear",
                ok: "OK",
                cancel: "Cancel",
                operators: {
                    contains: "Contains",
                    doesnotcontain: "Does not contain",
                    startswith: "Starts with",
                    endswith: "Ends with",
                    eq: "Is equal to",
                    neq: "Is not equal to"
                }
            }
        },

        _layout: function () {
            var options = this.options;

            this.wrapper = $(kendo.template(MENUTEMPLATE)({
                ns: kendo.ns,
                filterable: options.filterable,
                sortable: options.sortable,
                messages: options.messages
            }));

            this.menu = this.wrapper[MENU]({
                filter: options.filter,
                target: this.element,
                orientation: "vertical",
                showOn: "click",
                closeOnClick: false,
                open: proxy(this._menuOpen, this),
                select: proxy(this._select, this),
                copyAnchorStyles: false
            }).data(MENU);

            this._createWindow();

            if (options.filterable) {
                this._initFilterForm();
            }
        },

        _initFilterForm: function () {
            var filterForm = this.menu.element.find(".k-filter-item");
            var filterProxy = proxy(this._filter, this);

            this._filterOperator = new kendo.ui.DropDownList(filterForm.find("select"));
            this._filterValue = filterForm.find(".k-textbox");


            filterForm
                .on("submit" + NS, filterProxy)
                .on("click" + NS, ".k-button-filter", filterProxy)
                .on("click" + NS, ".k-button-clear", proxy(this._reset, this));
        },

        _setFilterForm: function (expression) {
            var operator = "";
            var value = "";

            if (expression) {
                operator = expression.operator;
                value = expression.value;
            }

            this._filterOperator.value(operator);
            this._filterValue.val(value);
        },

        _clearFilters: function (member) {
            var filter = this.dataSource.filter() || {};
            var expressions;
            var idx = 0;
            var length;

            filter.filters = filter.filters || [];
            expressions = findFilters(filter, member);

            for (length = expressions.length; idx < length; idx++) {
                filter.filters.splice(filter.filters.indexOf(expressions[idx]), 1);
            }

            return filter;
        },

        _filter: function (e) {
            var that = this;
            var value = that._filterValue.val();

            e.preventDefault();

            if (!value) {
                that.menu.close();
                return;
            }

            var expression = {
                field: that.currentMember,
                operator: that._filterOperator.value(),
                value: value
            };
            var filter = that._clearFilters(that.currentMember);

            filter.filters.push(expression);

            that.dataSource.filter(filter);
            that.menu.close();
        },

        _reset: function (e) {
            var that = this;
            var filter = that._clearFilters(that.currentMember);

            e.preventDefault();

            if (!filter.filters[0]) {
                filter = {};
            }

            that.dataSource.filter(filter);
            that._setFilterForm(null);
            that.menu.close();
        },

        _sort: function (dir) {
            var field = this.currentMember;
            var expressions = (this.dataSource.sort() || []);

            expressions = removeExpr(expressions, field);
            expressions.push({
                field: field,
                dir: dir
            });

            this.dataSource.sort(expressions);
            this.menu.close();
        },

        setDataSource: function (dataSource) {
            this.options.dataSource = dataSource;

            this._dataSource();
        },

        _dataSource: function () {
            this.dataSource = kendo.data.PivotDataSource.create(this.options.dataSource);
        },

        _createWindow: function () {
            var messages = this.options.messages;

            this.includeWindow = $(kendo.template(WINDOWTEMPLATE)({
                messages: messages
            }))
            .on("click" + NS, ".k-button-ok", proxy(this._applyIncludes, this))
            .on("click" + NS, ".k-button-cancel", proxy(this._closeWindow, this));

            this.includeWindow = new ui.Window(this.includeWindow, {
                title: messages.title,
                visible: false,
                resizable: false,
                open: proxy(this._windowOpen, this)
            });
        },

        _applyIncludes: function (e) {
            var checkedNodes = [];
            var resultExpression;
            var view = this.treeView.dataSource.view();
            var rootChecked = view[0].checked;
            var filter = this.dataSource.filter();
            var existingExpression = findFilters(filter, this.currentMember, "in")[0];

            checkedNodeIds(view, checkedNodes);

            if (existingExpression) {
                if (rootChecked) {
                    filter.filters.splice(filter.filters.indexOf(existingExpression), 1);
                    if (!filter.filters.length) {
                        filter = {};
                    }
                } else {
                    existingExpression.value = checkedNodes.join(",");
                }

                resultExpression = filter;
            }

            if (checkedNodes.length) {
                if (!resultExpression && !rootChecked) {
                    resultExpression = {
                        field: this.currentMember,
                        operator: "in",
                        value: checkedNodes.join(",")
                    };

                    if (filter) {
                        filter.filters.push(resultExpression);
                        resultExpression = filter;
                    }
                }
            }

            if (resultExpression) {
                this.dataSource.filter(resultExpression);
            }

            this._closeWindow(e);
        },

        _closeWindow: function (e) {
            e.preventDefault();

            this.includeWindow.close();
        },

        _treeViewDataSource: function () {
            var that = this;

            return kendo.data.HierarchicalDataSource.create({
                schema: {
                    model: {
                        id: "uniqueName",
                        hasChildren: function (item) {
                            return parseInt(item.childrenCardinality, 10) > 0;
                        }
                    }
                },
                transport: {
                    read: function (options) {
                        var restrictions = {};
                        var node = that.treeView.dataSource.get(options.data.uniqueName);
                        var name = options.data.uniqueName;

                        if (!name) {
                            restrictions.levelUniqueName = that.currentMember + ".[(ALL)]";
                        } else {
                            restrictions.memberUniqueName = node.uniqueName.replace(/\&/g, "&amp;");
                            restrictions.treeOp = 1;
                        }

                        that.dataSource
                            .schemaMembers(restrictions)
                            .done(function (data) {
                                checkNodes(that.dataSource.filter(), that.currentMember, data);

                                options.success(data);
                            })
                            .fail(options.error);
                    }
                }
            });
        },

        _createTreeView: function (element) {
            var that = this;

            that.treeView = new ui.TreeView(element, {
                autoBind: false,
                dataSource: that._treeViewDataSource(),
                dataTextField: "name",
                checkboxes: {
                    checkChildren: true
                },
                dataBound: function () {
                    ui.progress(that.includeWindow.element, false);
                }
            });
        },

        _menuOpen: function (e) {
            if (!e.event) {
                return;
            }

            var attr = kendo.attr("name");
            var expression;

            this.currentMember = $(e.event.target).closest("[" + attr + "]").attr(attr);

            if (this.options.filterable) {
                this._setFilterForm(findFilters(this.dataSource.filter(), this.currentMember)[0]);
            }
        },

        _select: function (e) {
            var item = $(e.item);

            $(".k-pivot-filter-window").not(this.includeWindow.element).kendoWindow("close");

            if (item.hasClass("k-include-item")) {
                this.includeWindow.center().open();
            } else if (item.hasClass("k-sort-asc")) {
                this._sort("asc");
            } else if (item.hasClass("k-sort-desc")) {
                this._sort("desc");
            }
        },

        _windowOpen: function () {
            if (!this.treeView) {
                this._createTreeView(this.includeWindow.element.find(".k-treeview"));
            }

            ui.progress(this.includeWindow.element, true);
            this.treeView.dataSource.read();
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            if (this.menu) {
                this.menu.destroy();
                this.menu = null;
            }

            if (this.reeeView) {
                this.treeView.destroy();
                this.treeView = null;
            }

            if (this.includeWindow) {
                this.includeWindow.destroy();
                this.includeWindow = null;
            }

            this.wrapper = null;
            this.element = null;
        }
    });

    function removeExpr(expressions, name) {
        var result = [];

        for (var idx = 0, length = expressions.length; idx < length; idx++) {
            if (expressions[idx].field !== name) {
                result.push(expressions[idx]);
            }
        }

        return result;
    }

    function findFilters(filter, member, operator) {
        if (!filter) {
            return [];
        }

        filter = filter.filters;

        var idx = 0;
        var result = [];
        var length = filter.length;
        var filterOperator;

        for (; idx < length; idx++) {
            filterOperator = filter[idx].operator;

            if (((!operator && filterOperator !== "in") || (filterOperator === operator)) && filter[idx].field === member) {
                result.push(filter[idx]);
            }
        }

        return result;
    }

    function checkNodes(filter, member, nodes) {
        var values, idx = 0, length = nodes.length;
        filter = findFilters(filter, member, "in")[0];

        if (!filter) {
            for (; idx < length; idx++) {
                nodes[idx].checked = true;
            }
        } else {
            values = filter.value.split(",");
            for (; idx < length; idx++) {
                nodes[idx].checked = $.inArray(nodes[idx].uniqueName, values) >= 0;
            }
        }
    }

    function checkedNodeIds(nodes, checkedNodes) {
        var idx, length = nodes.length;

        for (idx = 0; idx < length; idx++) {
            if (nodes[idx].checked && nodes[idx].level() !== 0) {
                checkedNodes.push(nodes[idx].uniqueName);
            }

            if (nodes[idx].hasChildren) {
                checkedNodeIds(nodes[idx].children.view(), checkedNodes);
            }
        }
    }

    var LABELMENUTEMPLATE =
        '<div class="k-filterable k-content" tabindex="-1" data-role="fieldmenu">' +
            '<form class="k-filter-menu">' +
                '<div>' +
                    '<div class="k-filter-help-text">#=messages.info#</div>' +
                    '<select>' +
                        '#for(var op in messages.operators){#' +
                            '<option value="#=op#">#=messages.operators[op]#</option>' +
                        '#}#' +
                    '</select>' +
                    '<input class="k-textbox" type="text" />' +
                    '<div>' +
                    '<a class="k-button k-primary k-button-filter" href="\\#">#=messages.filter#</a>' +
                    '<a class="k-button k-button-clear" href="\\#">#=messages.clear#</a>' +
                    '</div>' +
                '</div>' +
            '</form>' +
        '</div>';

    var MENUTEMPLATE = '<ul class="k-pivot-fieldmenu">' +
                        '# if (sortable) {#' +
                        '<li class="k-item k-sort-asc">' +
                            '<span class="k-link">' +
                                '<span class="k-icon k-i-sort-asc"></span>' +
                                '${messages.sortAscending}' +
                            '</span>' +
                        '</li>' +
                        '<li class="k-item k-sort-desc">' +
                            '<span class="k-link">' +
                                '<span class="k-icon k-i-sort-desc"></span>' +
                                '${messages.sortDescending}' +
                            '</span>' +
                        '</li>' +
                            '# if (filterable) {#' +
                            '<li class="k-separator"></li>' +
                            '# } #' +
                        '# } #' +
                        '# if (filterable) {#' +
                        '<li class="k-item k-include-item">' +
                            '<span class="k-link">' +
                                '<span class="k-icon k-filter"></span>' +
                                '${messages.include}' +
                            '</span>' +
                        '</li>' +
                        '<li class="k-separator"></li>' +
                        '<li class="k-item k-filter-item">' +
                            '<span class="k-link">' +
                                '<span class="k-icon k-filter"></span>' +
                                '${messages.filterFields}' +
                            '</span>' +
                            '<ul>' +
                                '<li>' + LABELMENUTEMPLATE + '</li>' +
                            '</ul>' +
                        '</li>' +
                        '# } #' +
                    '</ul>';

    var WINDOWTEMPLATE = '<div class="k-popup-edit-form k-pivot-filter-window"><div class="k-edit-form-container">' +
                            '<div class="k-treeview"></div>' +
                            '<div class="k-edit-buttons k-state-default">' +
                            '<a class="k-button k-primary k-button-ok" href="\\#">' +
                                '${messages.ok}' +
                            '</a>' +
                            '<a class="k-button k-button-cancel" href="\\#">' +
                                '${messages.cancel}' +
                            '</a>' +
                        '</div></div>';

    ui.plugin(PivotFieldMenu);

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        DataSource = kendo.data.DataSource,
        Widget = ui.Widget,
        CHANGE = "change",
        BOOL = "boolean",
        ENUM = "enums",
        STRING = "string",
        NS = ".kendoFilterCell",
        EQ = "Is equal to",
        NEQ = "Is not equal to",
        proxy = $.proxy;

    function findFilterForField(filter, field) {
        var filters = [];
        if ($.isPlainObject(filter)) {
            if (filter.hasOwnProperty("filters")) {
                filters = filter.filters;
            } else if (filter.field == field) {
                return filter;
            }
        }
        if (($.isArray(filter))) {
            filters = filter;
        }

        for (var i = 0; i < filters.length; i++) {
            var result = findFilterForField(filters[i], field);
            if (result) {
                return result;
            }
        }
    }

    function removeFiltersForField(expression, field) {
        if (expression.filters) {
            expression.filters = $.grep(expression.filters, function (filter) {
                removeFiltersForField(filter, field);
                if (filter.filters) {
                    return filter.filters.length;
                } else {
                    return filter.field != field;
                }
            });
        }
    }

    function removeDuplicates(dataSelector, dataTextField) {
        var getter = kendo.getter(dataTextField, true);

        return function (e) {
            var items = dataSelector(e),
                result = [],
                index = 0,
                seen = {};

            while (index < items.length) {
                var item = items[index++],
                    text = getter(item);

                if (!seen.hasOwnProperty(text)) {
                    result.push(item);
                    seen[text] = true;
                }
            }

            return result;
        };
    }

    var FilterCell = Widget.extend({
        init: function (element, options) {
            element = $(element).addClass("k-filtercell");
            var wrapper = this.wrapper = $("<span/>").appendTo(element);
            var that = this,
                dataSource,
                viewModel,
                type,
                operators = that.operators = options.operators || {},
                input = that.input = $("<input/>")
                    .attr(kendo.attr("bind"), "value: value")
                    .appendTo(wrapper);

            Widget.fn.init.call(that, element[0], options);
            options = that.options;
            dataSource = that.dataSource = options.dataSource;

            //gets the type from the dataSource or sets default to string
            that.model = dataSource.reader.model;
            type = options.type = STRING;
            var fields = kendo.getter("reader.model.fields", true)(dataSource) || {};
            var target = fields[options.field];
            if (target && target.type) {
                type = options.type = target.type;
            }
            if (options.values) {
                options.type = type = ENUM;
            }

            operators = operators[type] || options.operators[type];

            that._parse = function (value) {
                return value + "";
            };

            if (that.model && that.model.fields) {
                var field = that.model.fields[options.field];

                if (field) {
                    if (field.parse) {
                        that._parse = proxy(field.parse, field);
                    }
                }
            }

            that.viewModel = viewModel = kendo.observable({
                operator: options.operator,
                value: null,
                operatorVisible: function () {
                    var val = this.get("value");
                    return val !== null && val !== undefined && val != "undefined";
                }
            });
            viewModel.bind(CHANGE, proxy(that.updateDsFilter, that));

            if (type == STRING) {
                that.initSuggestDataSource(options);
            }

            if (options.inputWidth !== null) {
                input.width(options.inputWidth);
            }
            that._setInputType(options, type);

            if (type != BOOL && options.showOperators !== false) {
                that._createOperatorDropDown(operators);
            } else {
                wrapper.addClass("k-operator-hidden");
            }

            that._createClearIcon();

            kendo.bind(this.wrapper, viewModel);

            if (type == STRING) {
                if (!options.template) {
                    that.setAutoCompleteSource();
                }
            }

            if (type == ENUM) {
                that.setComboBoxSource(that.options.values);
            }

            that._refreshUI();

            that._refreshHandler = proxy(that._refreshUI, that);

            that.dataSource.bind(CHANGE, that._refreshHandler);

        },

        _setInputType: function (options, type) {
            var that = this,
                input = that.input;

            if (typeof (options.template) == "function") {
                options.template.call(that.viewModel, {
                    element: that.input,
                    dataSource: that.suggestDataSource
                });
            } else if (type == STRING) {
                input.attr(kendo.attr("role"), "autocomplete")
                        .attr(kendo.attr("text-field"), options.dataTextField || options.field)
                        .attr(kendo.attr("filter"), options.suggestionOperator)
                        .attr(kendo.attr("delay"), options.delay)
                        .attr(kendo.attr("min-length"), options.minLength)
                        .attr(kendo.attr("value-primitive"), true);
            } else if (type == "date") {
                input.attr(kendo.attr("role"), "datepicker");
            } else if (type == BOOL) {
                input.remove();
                var radioInput = $("<input type='radio'/>");
                var wrapper = that.wrapper;
                var inputName = kendo.guid();

                var labelTrue = $("<label/>").text(options.messages.isTrue).append(radioInput);
                radioInput.attr(kendo.attr("bind"), "checked:value")
                    .attr("name", inputName)
                    .val("true");

                var labelFalse = labelTrue.clone().text(options.messages.isFalse);
                radioInput.clone().val("false").appendTo(labelFalse);
                wrapper.append([labelTrue, labelFalse]);

            } else if (type == "number") {
                input.attr(kendo.attr("role"), "numerictextbox");
            } else if (type == ENUM) {
                input.attr(kendo.attr("role"), "combobox")
                        .attr(kendo.attr("text-field"), "text")
                        .attr(kendo.attr("suggest"), true)
                        .attr(kendo.attr("filter"), "contains")
                        .attr(kendo.attr("value-field"), "value")
                        .attr(kendo.attr("value-primitive"), true);
            }
        },

        _createOperatorDropDown: function (operators) {
            var items = [];
            for (var prop in operators) {
                items.push({
                    text: operators[prop],
                    value: prop
                });
            }
            var dropdown = $('<input class="k-dropdown-operator" ' + kendo.attr("bind") + '="value: operator"/>').appendTo(this.wrapper);
            this.operatorDropDown = dropdown.kendoDropDownList({
                dataSource: items,
                dataTextField: "text",
                dataValueField: "value",
                open: function () {
                    //TODO calc this
                    this.popup.element.width(150);
                },
                valuePrimitive: true
            }).data("kendoDropDownList");

            this.operatorDropDown.wrapper.find(".k-i-arrow-s").removeClass("k-i-arrow-s").addClass("k-filter");
        },

        initSuggestDataSource: function (options) {
            var suggestDataSource = options.suggestDataSource;

            if (!(suggestDataSource instanceof DataSource)) {
                if (!options.customDataSource && suggestDataSource) {
                    suggestDataSource.group = undefined;
                }
                suggestDataSource =
                    this.suggestDataSource =
                        DataSource.create(suggestDataSource);


            }

            if (!options.customDataSource) {
                suggestDataSource._pageSize = undefined;
                suggestDataSource.reader.data = removeDuplicates(suggestDataSource.reader.data, this.options.field);
            }

            this.suggestDataSource = suggestDataSource;
        },

        setAutoCompleteSource: function () {
            var autoComplete = this.input.data("kendoAutoComplete");
            if (autoComplete) {
                autoComplete.setDataSource(this.suggestDataSource);
            }
        },

        setComboBoxSource: function (values) {
            var dataSource = DataSource.create({
                data: values
            });
            var comboBox = this.input.data("kendoComboBox");
            if (comboBox) {
                comboBox.setDataSource(dataSource);
            }
        },

        _refreshUI: function (e) {
            var that = this,
                filter = findFilterForField(that.dataSource.filter(), this.options.field) || {},
                viewModel = that.viewModel;

            that.manuallyUpdatingVM = true;
            filter = $.extend(true, {}, filter);
            //MVVM check binding does not update the UI when changing the value to null/undefined
            if (that.options.type == BOOL) {
                if (viewModel.value !== filter.value) {
                    that.wrapper.find(":radio").prop("checked", false);
                }
            }

            if (filter.operator) {
                viewModel.set("operator", filter.operator);
            }
            viewModel.set("value", filter.value);
            that.manuallyUpdatingVM = false;
        },

        updateDsFilter: function (e) {
            var that = this,
                model = that.viewModel;

            if (that.manuallyUpdatingVM || (e.field == "operator" && model.value === undefined)) {
                return;
            }

            var currentFilter = $.extend({}, that.viewModel.toJSON(), { field: that.options.field });

            var expression = {
                logic: "and",
                filters: []
            };

            if (currentFilter.value !== undefined && currentFilter.value !== null) {
                expression.filters.push(currentFilter);
            }
            var mergeResult = that._merge(expression);
            if (mergeResult.filters.length) {
                that.dataSource.filter(mergeResult);
            } else {
                that.dataSource.filter({});
            }
        },

        _merge: function (expression) {
            var that = this,
                logic = expression.logic || "and",
                filters = expression.filters,
                filter,
                result = that.dataSource.filter() || { filters: [], logic: "and" },
                idx,
                length;

            removeFiltersForField(result, that.options.field);

            for (idx = 0, length = filters.length; idx < length; idx++) {
                filter = filters[idx];
                filter.value = that._parse(filter.value);
            }

            filters = $.grep(filters, function (filter) {
                return filter.value !== "" && filter.value !== null;
            });

            if (filters.length) {
                if (result.filters.length) {
                    expression.filters = filters;

                    if (result.logic !== "and") {
                        result.filters = [{ logic: result.logic, filters: result.filters}];
                        result.logic = "and";
                    }

                    if (filters.length > 1) {
                        result.filters.push(expression);
                    } else {
                        result.filters.push(filters[0]);
                    }
                } else {
                    result.filters = filters;
                    result.logic = logic;
                }
            }

            return result;
        },

        _createClearIcon: function () {
            var that = this;

            $("<button type='button' class='k-button k-button-icon'/>")
                .attr(kendo.attr("bind"), "visible:operatorVisible")
                .html("<span class='k-icon k-i-close'/>")
                .click(proxy(that.clearFilter, that))
                .appendTo(that.wrapper);
        },

        clearFilter: function () {
            this.viewModel.set("value", null);
        },

        destroy: function () {
            var that = this;

            that.filterModel = null;

            Widget.fn.destroy.call(that);

            kendo.destroy(that.element);
        },

        events: [
            CHANGE
        ],

        options: {
            name: "FilterCell",
            delay: 200,
            minLength: 1,
            inputWidth: null,
            values: undefined,
            customDataSource: false,
            field: "",
            dataTextField: "",
            type: "string",
            suggestDataSource: null,
            suggestionOperator: "startswith",
            operator: "eq",
            showOperators: true,
            template: null,
            messages: {
                isTrue: "is true",
                isFalse: "is false",
                filter: "Filter",
                clear: "Clear",
                operator: "Operator"
            },
            operators: {
                string: {
                    eq: EQ,
                    neq: NEQ,
                    startswith: "Starts with",
                    contains: "Contains",
                    doesnotcontain: "Does not contain",
                    endswith: "Ends with"
                },
                number: {
                    eq: EQ,
                    neq: NEQ,
                    gte: "Is greater than or equal to",
                    gt: "Is greater than",
                    lte: "Is less than or equal to",
                    lt: "Is less than"
                },
                date: {
                    eq: EQ,
                    neq: NEQ,
                    gte: "Is after or equal to",
                    gt: "Is after",
                    lte: "Is before or equal to",
                    lt: "Is before"
                },
                enums: {
                    eq: EQ,
                    neq: NEQ
                }
            }
        }
    });

    ui.plugin(FilterCell);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        keys = kendo.keys,
        extend = $.extend,
        each = $.each,
        template = kendo.template,
        Widget = ui.Widget,
        excludedNodesRegExp = /^(ul|a|div)$/i,
        NS = ".kendoPanelBar",
        IMG = "img",
        HREF = "href",
        LAST = "k-last",
        LINK = "k-link",
        LINKSELECTOR = "." + LINK,
        ERROR = "error",
        ITEM = ".k-item",
        GROUP = ".k-group",
        VISIBLEGROUP = GROUP + ":visible",
        IMAGE = "k-image",
        FIRST = "k-first",
        EXPAND = "expand",
        SELECT = "select",
        CONTENT = "k-content",
        ACTIVATE = "activate",
        COLLAPSE = "collapse",
        MOUSEENTER = "mouseenter",
        MOUSELEAVE = "mouseleave",
        CONTENTLOAD = "contentLoad",
        ACTIVECLASS = "k-state-active",
        GROUPS = "> .k-panel",
        CONTENTS = "> .k-content",
        FOCUSEDCLASS = "k-state-focused",
        DISABLEDCLASS = "k-state-disabled",
        SELECTEDCLASS = "k-state-selected",
        SELECTEDSELECTOR = "." + SELECTEDCLASS,
        HIGHLIGHTCLASS = "k-state-highlight",
        ACTIVEITEMSELECTOR = ITEM + ":not(.k-state-disabled)",
        clickableItems = ACTIVEITEMSELECTOR + " > .k-link",
        disabledItems = ITEM + ".k-state-disabled > .k-link",
        selectableItems = "> li > " + SELECTEDSELECTOR + ", .k-panel > li > " + SELECTEDSELECTOR,
        defaultState = "k-state-default",
        ARIA_DISABLED = "aria-disabled",
        ARIA_EXPANDED = "aria-expanded",
        ARIA_HIDDEN = "aria-hidden",
        ARIA_SELECTED = "aria-selected",
        VISIBLE = ":visible",
        EMPTY = ":empty",
        SINGLE = "single",

        templates = {
            content: template(
                "<div role='region' class='k-content'#= contentAttributes(data) #>#= content(item) #</div>"
            ),
            group: template(
                "<ul role='group' aria-hidden='true' class='#= groupCssClass(group) #'#= groupAttributes(group) #>" +
                    "#= renderItems(data) #" +
                "</ul>"
            ),
            itemWrapper: template(
                "<#= tag(item) # class='#= textClass(item, group) #' #= contentUrl(item) ##= textAttributes(item) #>" +
                    "#= image(item) ##= sprite(item) ##= text(item) #" +
                    "#= arrow(data) #" +
                "</#= tag(item) #>"
            ),
            item: template(
                "<li role='menuitem' #=aria(item)#class='#= wrapperCssClass(group, item) #'>" +
                    "#= itemWrapper(data) #" +
                    "# if (item.items) { #" +
                    "#= subGroup({ items: item.items, panelBar: panelBar, group: { expanded: item.expanded } }) #" +
                    "# } else if (item.content || item.contentUrl) { #" +
                    "#= renderContent(data) #" +
                    "# } #" +
                "</li>"
            ),
            image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
            arrow: template("<span class='#= arrowClass(item) #'></span>"),
            sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
            empty: template("")
        },

        rendering = {
            aria: function (item) {
                var attr = "";

                if (item.items || item.content || item.contentUrl) {
                    attr += ARIA_EXPANDED + "='" + (item.expanded ? "true" : "false") + "' ";
                }

                if (item.enabled === false) {
                    attr += ARIA_DISABLED + "='true'";
                }

                return attr;
            },

            wrapperCssClass: function (group, item) {
                var result = "k-item",
                    index = item.index;

                if (item.enabled === false) {
                    result += " " + DISABLEDCLASS;
                } else if (item.expanded === true) {
                    result += " " + ACTIVECLASS;
                } else {
                    result += " k-state-default";
                }

                if (index === 0) {
                    result += " k-first";
                }

                if (index == group.length - 1) {
                    result += " k-last";
                }

                if (item.cssClass) {
                    result += " " + item.cssClass;
                }

                return result;
            },

            textClass: function (item, group) {
                var result = LINK;

                if (group.firstLevel) {
                    result += " k-header";
                }

                return result;
            },
            textAttributes: function (item) {
                return item.url ? " href='" + item.url + "'" : "";
            },
            arrowClass: function (item) {
                var result = "k-icon";

                result += item.expanded ? " k-i-arrow-n k-panelbar-collapse" : " k-i-arrow-s k-panelbar-expand";

                return result;
            },
            text: function (item) {
                return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
            },
            tag: function (item) {
                return item.url || item.contentUrl ? "a" : "span";
            },
            groupAttributes: function (group) {
                return group.expanded !== true ? " style='display:none'" : "";
            },
            groupCssClass: function () {
                return "k-group k-panel";
            },
            contentAttributes: function (content) {
                return content.item.expanded !== true ? " style='display:none'" : "";
            },
            content: function (item) {
                return item.content ? item.content : item.contentUrl ? "" : "&nbsp;";
            },
            contentUrl: function (item) {
                return item.contentUrl ? 'href="' + item.contentUrl + '"' : "";
            }
        };

    function updateArrow(items) {
        items = $(items);

        items.children(LINKSELECTOR).children(".k-icon").remove();

        items
            .filter(":has(.k-panel),:has(.k-content)")
            .children(".k-link:not(:has([class*=k-i-arrow]))")
            .each(function () {
                var item = $(this),
                    parent = item.parent();

                item.append("<span class='k-icon " + (parent.hasClass(ACTIVECLASS) ? "k-i-arrow-n k-panelbar-collapse" : "k-i-arrow-s k-panelbar-expand") + "'/>");
            });
    }

    function updateFirstLast(items) {
        items = $(items);

        items.filter(".k-first:not(:first-child)").removeClass(FIRST);
        items.filter(".k-last:not(:last-child)").removeClass(LAST);
        items.filter(":first-child").addClass(FIRST);
        items.filter(":last-child").addClass(LAST);
    }

    var PanelBar = Widget.extend({
        init: function (element, options) {
            var that = this,
                content;

            Widget.fn.init.call(that, element, options);

            element = that.wrapper = that.element.addClass("k-widget k-reset k-header k-panelbar");
            options = that.options;

            if (element[0].id) {
                that._itemId = element[0].id + "_pb_active";
            }

            that._tabindex();

            that._initData(options);

            that._updateClasses();

            that._animations(options);

            element
                .on("click" + NS, clickableItems, function (e) {
                    if (that._click($(e.currentTarget))) {
                        e.preventDefault();
                    }
                })
                .on(MOUSEENTER + NS + " " + MOUSELEAVE + NS, clickableItems, that._toggleHover)
                .on("click" + NS, disabledItems, false)
                .on("keydown" + NS, $.proxy(that._keydown, that))
                .on("focus" + NS, function () {
                    var item = that.select();
                    that._current(item[0] ? item : that._first());
                })
                .on("blur" + NS, function () {
                    that._current(null);
                })
                .attr("role", "menu");

            content = element.find("li." + ACTIVECLASS + " > ." + CONTENT);

            if (content[0]) {
                that.expand(content.parent(), false);
            }

            kendo.notify(that);
        },

        events: [
            EXPAND,
            COLLAPSE,
            SELECT,
            ACTIVATE,
            ERROR,
            CONTENTLOAD
        ],
        options: {
            name: "PanelBar",
            animation: {
                expand: {
                    effects: "expand:vertical",
                    duration: 200
                },
                collapse: { // if collapse animation effects are defined, they will be used instead of expand.reverse
                    duration: 200
                }
            },
            expandMode: "multiple"
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            this.element.off(NS);

            kendo.destroy(this.element);
        },

        _initData: function (options) {
            var that = this;

            if (options.dataSource) {
                that.element.empty();
                that.append(options.dataSource, that.element);
            }
        },

        setOptions: function (options) {
            var animation = this.options.animation;

            this._animations(options);

            options.animation = extend(true, animation, options.animation);

            if ("dataSource" in options) {
                this._initData(options);
            }

            Widget.fn.setOptions.call(this, options);
        },

        expand: function (element, useAnimation) {
            var that = this,
                animBackup = {};
            useAnimation = useAnimation !== false;
            element = this.element.find(element);

            element.each(function (index, item) {
                item = $(item);
                var groups = item.find(GROUPS).add(item.find(CONTENTS));

                if (!item.hasClass(DISABLEDCLASS) && groups.length > 0) {

                    if (that.options.expandMode == SINGLE && that._collapseAllExpanded(item)) {
                        return that;
                    }

                    element.find("." + HIGHLIGHTCLASS).removeClass(HIGHLIGHTCLASS);
                    item.addClass(HIGHLIGHTCLASS);

                    if (!useAnimation) {
                        animBackup = that.options.animation;
                        that.options.animation = { expand: { effects: {} }, collapse: { hide: true, effects: {}} };
                    }

                    if (!that._triggerEvent(EXPAND, item)) {
                        that._toggleItem(item, false);
                    }

                    if (!useAnimation) {
                        that.options.animation = animBackup;
                    }
                }
            });

            return that;
        },

        collapse: function (element, useAnimation) {
            var that = this,
                animBackup = {};
            useAnimation = useAnimation !== false;
            element = that.element.find(element);

            element.each(function (index, item) {
                item = $(item);
                var groups = item.find(GROUPS).add(item.find(CONTENTS));

                if (!item.hasClass(DISABLEDCLASS) && groups.is(VISIBLE)) {
                    item.removeClass(HIGHLIGHTCLASS);

                    if (!useAnimation) {
                        animBackup = that.options.animation;
                        that.options.animation = { expand: { effects: {} }, collapse: { hide: true, effects: {}} };
                    }

                    if (!that._triggerEvent(COLLAPSE, item)) {
                        that._toggleItem(item, true);
                    }

                    if (!useAnimation) {
                        that.options.animation = animBackup;
                    }
                }

            });

            return that;
        },

        _toggleDisabled: function (element, enable) {
            element = this.element.find(element);
            element
                .toggleClass(defaultState, enable)
                .toggleClass(DISABLEDCLASS, !enable)
                .attr(ARIA_DISABLED, !enable);
        },

        select: function (element) {
            var that = this;

            if (element === undefined) {
                return that.element.find(selectableItems).parent();
            }

            element = that.element.find(element);

            if (!element.length) {
                this._updateSelected(element);
            } else {
                element
                    .each(function () {
                        var item = $(this),
                            link = item.children(LINKSELECTOR);

                        if (item.hasClass(DISABLEDCLASS)) {
                            return that;
                        }

                        if (!that._triggerEvent(SELECT, item)) {
                            that._updateSelected(link);
                        }
                    });
            }

            return that;
        },

        clearSelection: function () {
            this.select($());
        },

        enable: function (element, state) {
            this._toggleDisabled(element, state !== false);

            return this;
        },

        disable: function (element) {
            this._toggleDisabled(element, false);

            return this;
        },

        append: function (item, referenceItem) {
            referenceItem = this.element.find(referenceItem);

            var inserted = this._insert(item, referenceItem, referenceItem.length ? referenceItem.find(GROUPS) : null);

            each(inserted.items, function () {
                inserted.group.append(this);
                updateFirstLast(this);
            });

            updateArrow(referenceItem);
            updateFirstLast(inserted.group.find(".k-first, .k-last"));
            inserted.group.height("auto");

            return this;
        },

        insertBefore: function (item, referenceItem) {
            referenceItem = this.element.find(referenceItem);

            var inserted = this._insert(item, referenceItem, referenceItem.parent());

            each(inserted.items, function () {
                referenceItem.before(this);
                updateFirstLast(this);
            });

            updateFirstLast(referenceItem);
            inserted.group.height("auto");

            return this;
        },

        insertAfter: function (item, referenceItem) {
            referenceItem = this.element.find(referenceItem);

            var inserted = this._insert(item, referenceItem, referenceItem.parent());

            each(inserted.items, function () {
                referenceItem.after(this);
                updateFirstLast(this);
            });

            updateFirstLast(referenceItem);
            inserted.group.height("auto");

            return this;
        },

        remove: function (element) {
            element = this.element.find(element);

            var that = this,
                parent = element.parentsUntil(that.element, ITEM),
                group = element.parent("ul");

            element.remove();

            if (group && !group.hasClass("k-panelbar") && !group.children(ITEM).length) {
                group.remove();
            }

            if (parent.length) {
                parent = parent.eq(0);

                updateArrow(parent);
                updateFirstLast(parent);
            }

            return that;
        },

        reload: function (element) {
            var that = this;
            element = that.element.find(element);

            element.each(function () {
                var item = $(this);

                that._ajaxRequest(item, item.children("." + CONTENT), !item.is(VISIBLE));
            });
        },

        _first: function () {
            return this.element.children(ACTIVEITEMSELECTOR).first();
        },

        _last: function () {
            var item = this.element.children(ACTIVEITEMSELECTOR).last(),
                group = item.children(VISIBLEGROUP);

            if (group[0]) {
                return group.children(ACTIVEITEMSELECTOR).last();
            }
            return item;
        },

        _current: function (candidate) {
            var that = this,
                focused = that._focused,
                id = that._itemId;

            if (candidate === undefined) {
                return focused;
            }

            that.element.removeAttr("aria-activedescendant");

            if (focused) {
                if (focused[0].id === id) {
                    focused.removeAttr("id");
                }

                focused
                    .children(LINKSELECTOR)
                    .removeClass(FOCUSEDCLASS);
            }

            if (candidate) {
                id = candidate[0].id || id;

                candidate.attr("id", id)
                         .children(LINKSELECTOR)
                         .addClass(FOCUSEDCLASS);

                that.element.attr("aria-activedescendant", id);
            }

            that._focused = candidate;
        },

        _keydown: function (e) {
            var that = this,
                key = e.keyCode,
                current = that._current();

            if (e.target != e.currentTarget) {
                return;
            }

            if (key == keys.DOWN || key == keys.RIGHT) {
                that._current(that._nextItem(current));
                e.preventDefault();
            } else if (key == keys.UP || key == keys.LEFT) {
                that._current(that._prevItem(current));
                e.preventDefault();
            } else if (key == keys.ENTER || key == keys.SPACEBAR) {
                that._click(current.children(LINKSELECTOR));
                e.preventDefault();
            } else if (key == keys.HOME) {
                that._current(that._first());
                e.preventDefault();
            } else if (key == keys.END) {
                that._current(that._last());
                e.preventDefault();
            }
        },

        _nextItem: function (item) {
            if (!item) {
                return this._first();
            }

            var group = item.children(VISIBLEGROUP),
                next = item.nextAll(":visible").first();

            if (group[0]) {
                next = group.children("." + FIRST);
            }

            if (!next[0]) {
                next = item.parent(VISIBLEGROUP).parent(ITEM).next();
            }

            if (!next[0]) {
                next = this._first();
            }

            if (next.hasClass(DISABLEDCLASS)) {
                next = this._nextItem(next);
            }

            return next;
        },

        _prevItem: function (item) {
            if (!item) {
                return this._last();
            }

            var prev = item.prevAll(":visible").first(),
                result;

            if (!prev[0]) {
                prev = item.parent(VISIBLEGROUP).parent(ITEM);
                if (!prev[0]) {
                    prev = this._last();
                }
            } else {
                result = prev;
                while (result[0]) {
                    result = result.children(VISIBLEGROUP).children("." + LAST);
                    if (result[0]) {
                        prev = result;
                    }
                }
            }

            if (prev.hasClass(DISABLEDCLASS)) {
                prev = this._prevItem(prev);
            }

            return prev;
        },

        _insert: function (item, referenceItem, parent) {
            var that = this,
                items,
                plain = $.isPlainObject(item),
                isReferenceItem = referenceItem && referenceItem[0],
                groupData;

            if (!isReferenceItem) {
                parent = that.element;
            }

            groupData = {
                firstLevel: parent.hasClass("k-panelbar"),
                expanded: parent.parent().hasClass(ACTIVECLASS),
                length: parent.children().length
            };

            if (isReferenceItem && !parent.length) {
                parent = $(PanelBar.renderGroup({ group: groupData })).appendTo(referenceItem);
            }

            if (plain || $.isArray(item)) { // is JSON
                items = $.map(plain ? [item] : item, function (value, idx) {
                    if (typeof value === "string") {
                        return $(value);
                    } else {
                        return $(PanelBar.renderItem({
                            group: groupData,
                            item: extend(value, { index: idx })
                        }));
                    }
                });

                if (isReferenceItem) {
                    referenceItem.attr(ARIA_EXPANDED, false);
                }
            } else {
                if (typeof item == "string" && item[0] != "<") {
                    items = that.element.find(item);
                } else {
                    items = $(item);
                }
                that._updateItemsClasses(items);
            }

            return { items: items, group: parent };
        },

        _toggleHover: function (e) {
            var target = $(e.currentTarget);

            if (!target.parents("li." + DISABLEDCLASS).length) {
                target.toggleClass("k-state-hover", e.type == MOUSEENTER);
            }
        },

        _updateClasses: function () {
            var that = this,
                panels, items;

            panels = that.element
                         .find("li > ul")
                         .not(function () { return $(this).parentsUntil(".k-panelbar", "div").length; })
                         .addClass("k-group k-panel")
                         .attr("role", "group");

            panels.parent()
                  .attr(ARIA_EXPANDED, false)
                  .not("." + ACTIVECLASS)
                  .children("ul")
                  .attr(ARIA_HIDDEN, true)
                  .hide();

            items = that.element.add(panels).children();

            that._updateItemsClasses(items);
            updateArrow(items);
            updateFirstLast(items);
        },

        _updateItemsClasses: function (items) {
            var length = items.length,
                idx = 0;

            for (; idx < length; idx++) {
                this._updateItemClasses(items[idx], idx);
            }
        },

        _updateItemClasses: function (item, index) {
            var selected = this._selected,
                contentUrls = this.options.contentUrls,
                url = contentUrls && contentUrls[index],
                root = this.element[0],
                wrapElement, link;

            item = $(item).addClass("k-item").attr("role", "menuitem");

            if (kendo.support.browser.msie) {  // IE10 doesn't apply list-style: none on invisible items otherwise.
                item.css("list-style-position", "inside")
                    .css("list-style-position", "");
            }

            item
                .children(IMG)
                .addClass(IMAGE);

            link = item
                    .children("a")
                    .addClass(LINK);

            if (link[0]) {
                link.attr("href", url); //url can be undefined

                link.children(IMG)
                    .addClass(IMAGE);
            }

            item
                .filter(":not([disabled]):not([class*=k-state])")
                .addClass("k-state-default");

            item
                .filter("li[disabled]")
                .addClass("k-state-disabled")
                .attr(ARIA_DISABLED, true)
                .removeAttr("disabled");

            item
                .children("div")
                .addClass(CONTENT)
                .attr("role", "region")
                .attr(ARIA_HIDDEN, true)
                .hide()
                .parent()
                .attr(ARIA_EXPANDED, false);

            link = item.children(SELECTEDSELECTOR);
            if (link[0]) {
                if (selected) {
                    selected.removeAttr(ARIA_SELECTED)
                            .children(SELECTEDSELECTOR)
                            .removeClass(SELECTEDCLASS);
                }

                link.addClass(SELECTEDCLASS);
                this._selected = item.attr(ARIA_SELECTED, true);
            }

            if (!item.children(LINKSELECTOR)[0]) {
                wrapElement = "<span class='" + LINK + "'/>";
                if (contentUrls && contentUrls[index] && item[0].parentNode == root) {
                    wrapElement = '<a class="k-link k-header" href="' + contentUrls[index] + '"/>';
                }

                item
                    .contents()      // exclude groups, real links, templates and empty text nodes
                    .filter(function () { return (!this.nodeName.match(excludedNodesRegExp) && !(this.nodeType == 3 && !$.trim(this.nodeValue))); })
                    .wrapAll(wrapElement);
            }

            if (item.parent(".k-panelbar")[0]) {
                item
                    .children(LINKSELECTOR)
                    .addClass("k-header");
            }
        },

        _click: function (target) {
            var that = this,
                element = that.element,
                prevent, contents, href, isAnchor;

            if (target.parents("li." + DISABLEDCLASS).length) {
                return;
            }

            if (target.closest(".k-widget")[0] != element[0]) {
                return;
            }

            var link = target.closest(LINKSELECTOR),
                item = link.closest(ITEM);

            that._updateSelected(link);

            contents = item.find(GROUPS).add(item.find(CONTENTS));
            href = link.attr(HREF);
            isAnchor = href && (href.charAt(href.length - 1) == "#" || href.indexOf("#" + that.element[0].id + "-") != -1);
            prevent = !!(isAnchor || contents.length);

            if (contents.data("animating")) {
                return prevent;
            }

            if (that._triggerEvent(SELECT, item)) {
                prevent = true;
            }

            if (prevent === false) {
                return;
            }

            if (that.options.expandMode == SINGLE) {
                if (that._collapseAllExpanded(item)) {
                    return prevent;
                }
            }

            if (contents.length) {
                var visibility = contents.is(VISIBLE);

                if (!that._triggerEvent(!visibility ? EXPAND : COLLAPSE, item)) {
                    prevent = that._toggleItem(item, visibility);
                }
            }

            return prevent;
        },

        _toggleItem: function (element, isVisible) {
            var that = this,
                childGroup = element.find(GROUPS),
                link = element.find(LINKSELECTOR),
                url = link.attr(HREF),
                prevent, content;

            if (childGroup.length) {
                this._toggleGroup(childGroup, isVisible);
                prevent = true;
            } else {
                content = element.children("." + CONTENT);

                if (content.length) {
                    prevent = true;

                    if (!content.is(EMPTY) || url === undefined) {
                        that._toggleGroup(content, isVisible);
                    } else {
                        that._ajaxRequest(element, content, isVisible);
                    }
                }
            }
            return prevent;
        },

        _toggleGroup: function (element, visibility) {
            var that = this,
                animationSettings = that.options.animation,
                animation = animationSettings.expand,
                collapse = extend({}, animationSettings.collapse),
                hasCollapseAnimation = collapse && "effects" in collapse;

            if (element.is(VISIBLE) != visibility) {
                return;
            }

            element
                .parent()
                .attr(ARIA_EXPANDED, !visibility)
                .attr(ARIA_HIDDEN, visibility)
                .toggleClass(ACTIVECLASS, !visibility)
                .find("> .k-link > .k-icon")
                    .toggleClass("k-i-arrow-n", !visibility)
                    .toggleClass("k-panelbar-collapse", !visibility)
                    .toggleClass("k-i-arrow-s", visibility)
                    .toggleClass("k-panelbar-expand", visibility);

            if (visibility) {
                animation = extend(hasCollapseAnimation ? collapse
                                    : extend({ reverse: true }, animation), { hide: true });
            } else {
                animation = extend({ complete: function (element) {
                    that._triggerEvent(ACTIVATE, element.closest(ITEM));
                } 
                }, animation);
            }

            element
                .kendoStop(true, true)
                .kendoAnimate(animation);
        },

        _collapseAllExpanded: function (item) {
            var that = this, children, stopExpand = false;

            var groups = item.find(GROUPS).add(item.find(CONTENTS));

            if (groups.is(VISIBLE)) {
                stopExpand = true;
            }

            if (!(groups.is(VISIBLE) || groups.length === 0)) {
                children = item.siblings();
                children.find(GROUPS).add(children.find(CONTENTS))
                        .filter(function () { return $(this).is(VISIBLE); })
                        .each(function (index, content) {
                            content = $(content);

                            stopExpand = that._triggerEvent(COLLAPSE, content.closest(ITEM));
                            if (!stopExpand) {
                                that._toggleGroup(content, true);
                            }
                        });
            }

            return stopExpand;
        },

        _ajaxRequest: function (element, contentElement, isVisible) {

            var that = this,
                statusIcon = element.find(".k-panelbar-collapse, .k-panelbar-expand"),
                link = element.find(LINKSELECTOR),
                loadingIconTimeout = setTimeout(function () {
                    statusIcon.addClass("k-loading");
                }, 100),
                data = {},
                url = link.attr(HREF);

            $.ajax({
                type: "GET",
                cache: false,
                url: url,
                dataType: "html",
                data: data,

                error: function (xhr, status) {
                    statusIcon.removeClass("k-loading");
                    if (that.trigger(ERROR, { xhr: xhr, status: status })) {
                        this.complete();
                    }
                },

                complete: function () {
                    clearTimeout(loadingIconTimeout);
                    statusIcon.removeClass("k-loading");
                },

                success: function (data) {
                    function getElements() {
                        return { elements: contentElement.get() };
                    }
                    try {
                        that.angular("cleanup", getElements);
                        contentElement.html(data);
                        that.angular("compile", getElements);
                    } catch (e) {
                        var console = window.console;

                        if (console && console.error) {
                            console.error(e.name + ": " + e.message + " in " + url);
                        }
                        this.error(this.xhr, "error");
                    }

                    that._toggleGroup(contentElement, isVisible);

                    that.trigger(CONTENTLOAD, { item: element[0], contentElement: contentElement[0] });
                }
            });
        },

        _triggerEvent: function (eventName, element) {
            var that = this;

            return that.trigger(eventName, { item: element[0] });
        },

        _updateSelected: function (link) {
            var that = this,
                element = that.element,
                item = link.parent(ITEM),
                selected = that._selected;

            if (selected) {
                selected.removeAttr(ARIA_SELECTED);
            }

            that._selected = item.attr(ARIA_SELECTED, true);

            element.find(selectableItems).removeClass(SELECTEDCLASS);
            element.find("> ." + HIGHLIGHTCLASS + ", .k-panel > ." + HIGHLIGHTCLASS).removeClass(HIGHLIGHTCLASS);

            link.addClass(SELECTEDCLASS);
            link.parentsUntil(element, ITEM).filter(":has(.k-header)").addClass(HIGHLIGHTCLASS);
            that._current(item[0] ? item : null);
        },

        _animations: function (options) {
            if (options && ("animation" in options) && !options.animation) {
                options.animation = { expand: { effects: {} }, collapse: { hide: true, effects: {}} };
            }
        }

    });

    // client-side rendering
    extend(PanelBar, {
        renderItem: function (options) {
            options = extend({ panelBar: {}, group: {} }, options);

            var empty = templates.empty,
                item = options.item;

            return templates.item(extend(options, {
                image: item.imageUrl ? templates.image : empty,
                sprite: item.spriteCssClass ? templates.sprite : empty,
                itemWrapper: templates.itemWrapper,
                renderContent: PanelBar.renderContent,
                arrow: item.items || item.content || item.contentUrl ? templates.arrow : empty,
                subGroup: PanelBar.renderGroup
            }, rendering));
        },

        renderGroup: function (options) {
            return templates.group(extend({
                renderItems: function (options) {
                    var html = "",
                        i = 0,
                        items = options.items,
                        len = items ? items.length : 0,
                        group = extend({ length: len }, options.group);

                    for (; i < len; i++) {
                        html += PanelBar.renderItem(extend(options, {
                            group: group,
                            item: extend({ index: i }, items[i])
                        }));
                    }

                    return html;
                }
            }, options, rendering));
        },

        renderContent: function (options) {
            return templates.content(extend(options, rendering));
        }
    });

    kendo.ui.plugin(PanelBar);

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        HORIZONTAL = "horizontal",
        VERTICAL = "vertical",
        DEFAULTMIN = 0,
        DEFAULTMAX = 100,
        DEFAULTVALUE = 0,
        DEFAULTCHUNKCOUNT = 5,
        KPROGRESSBAR = "k-progressbar",
        KPROGRESSBARREVERSE = "k-progressbar-reverse",
        KPROGRESSBARINDETERMINATE = "k-progressbar-indeterminate",
        KPROGRESSBARCOMPLETE = "k-complete",
        KPROGRESSWRAPPER = "k-state-selected",
        KPROGRESSSTATUS = "k-progress-status",
        KCOMPLETEDCHUNK = "k-state-selected",
        KUPCOMINGCHUNK = "k-state-default",
        KSTATEDISABLED = "k-state-disabled",
        PROGRESSTYPE = {
            VALUE: "value",
            PERCENT: "percent",
            CHUNK: "chunk"
        },
        CHANGE = "change",
        COMPLETE = "complete",
        BOOLEAN = "boolean",
        math = Math,
        extend = $.extend,
        proxy = $.proxy,
        HUNDREDPERCENT = 100,
        DEFAULTANIMATIONDURATION = 400,
        PRECISION = 3,
        templates = {
            progressStatus: "<span class='k-progress-status-wrap'><span class='k-progress-status'></span></span>"
        };

    var ProgressBar = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(this, element, options);

            options = that.options;

            that._progressProperty = (options.orientation === HORIZONTAL) ? "width" : "height";

            that._fields();

            options.value = that._validateValue(options.value);

            that._validateType(options.type);

            that._wrapper();

            that._progressAnimation();

            if ((options.value !== options.min) && (options.value !== false)) {
                that._updateProgress();
            }
        },

        setOptions: function (options) {
            var that = this;

            Widget.fn.setOptions.call(that, options);

            if (options.hasOwnProperty("reverse")) {
                that.wrapper.toggleClass("k-progressbar-reverse", options.reverse);
            }

            if (options.hasOwnProperty("enable")) {
                that.enable(options.enable);
            }

            that._progressAnimation();

            that._validateValue();

            that._updateProgress();
        },

        events: [
            CHANGE,
            COMPLETE
        ],

        options: {
            name: "ProgressBar",
            orientation: HORIZONTAL,
            reverse: false,
            min: DEFAULTMIN,
            max: DEFAULTMAX,
            value: DEFAULTVALUE,
            enable: true,
            type: PROGRESSTYPE.VALUE,
            chunkCount: DEFAULTCHUNKCOUNT,
            showStatus: true,
            animation: {}
        },

        _fields: function () {
            var that = this;

            that._isStarted = false;

            that.progressWrapper = that.progressStatus = $();
        },

        _validateType: function (currentType) {
            var isValid = false;

            $.each(PROGRESSTYPE, function (k, type) {
                if (type === currentType) {
                    isValid = true;
                    return false;
                }
            });

            if (!isValid) {
                throw new Error(kendo.format("Invalid ProgressBar type '{0}'", currentType));
            }
        },

        _wrapper: function () {
            var that = this;
            var container = that.wrapper = that.element;
            var options = that.options;
            var orientation = options.orientation;
            var initialStatusValue;

            container.addClass("k-widget " + KPROGRESSBAR);

            container.addClass(KPROGRESSBAR + "-" + ((orientation === HORIZONTAL) ? HORIZONTAL : VERTICAL));

            if (options.enable === false) {
                container.addClass(KSTATEDISABLED);
            }

            if (options.reverse) {
                container.addClass(KPROGRESSBARREVERSE);
            }

            if (options.value === false) {
                container.addClass(KPROGRESSBARINDETERMINATE);
            }

            if (options.type === PROGRESSTYPE.CHUNK) {
                that._addChunkProgressWrapper();
            } else {
                if (options.showStatus) {
                    that.progressStatus = that.wrapper.prepend(templates.progressStatus)
                                              .find("." + KPROGRESSSTATUS);

                    initialStatusValue = (options.value !== false) ? options.value : options.min;

                    if (options.type === PROGRESSTYPE.VALUE) {
                        that.progressStatus.text(initialStatusValue);
                    } else {
                        that.progressStatus.text(that._calculatePercentage(initialStatusValue) + "%");
                    }
                }
            }
        },

        value: function (value) {
            return this._value(value);
        },

        _value: function (value) {
            var that = this;
            var options = that.options;
            var validated;

            if (value === undefined) {
                return options.value;
            } else {
                if (typeof value !== BOOLEAN) {
                    value = that._roundValue(value);

                    if (!isNaN(value)) {
                        validated = that._validateValue(value);

                        if (validated !== options.value) {
                            that.wrapper.removeClass(KPROGRESSBARINDETERMINATE);

                            options.value = validated;

                            that._isStarted = true;

                            that._updateProgress();
                        }
                    }
                } else if (!value) {
                    that.wrapper.addClass(KPROGRESSBARINDETERMINATE);
                    options.value = false;
                }
            }
        },

        _roundValue: function (value) {
            value = parseFloat(value);

            var power = math.pow(10, PRECISION);

            return math.floor(value * power) / power;
        },

        _validateValue: function (value) {
            var that = this;
            var options = that.options;

            if (value !== false) {
                if (value <= options.min || value === true) {
                    return options.min;
                } else if (value >= options.max) {
                    return options.max;
                }
            } else if (value === false) {
                return false;
            }

            if (isNaN(that._roundValue(value))) {
                return options.min;
            }

            return value;
        },

        _updateProgress: function () {
            var that = this;
            var options = that.options;
            var percentage = that._calculatePercentage();

            if (options.type === PROGRESSTYPE.CHUNK) {
                that._updateChunks(percentage);
                that._onProgressUpdateAlways(options.value);
            } else {
                that._updateProgressWrapper(percentage);
            }
        },

        _updateChunks: function (percentage) {
            var that = this;
            var options = that.options;
            var chunkCount = options.chunkCount;
            var percentagesPerChunk = parseInt((HUNDREDPERCENT / chunkCount) * 100, 10) / 100;
            var percentageParsed = parseInt(percentage * 100, 10) / 100;
            var completedChunksCount = math.floor(percentageParsed / percentagesPerChunk);
            var completedChunks;

            if ((options.orientation === HORIZONTAL && !(options.reverse)) ||
               (options.orientation === VERTICAL && options.reverse)) {
                completedChunks = that.wrapper.find("li.k-item:lt(" + completedChunksCount + ")");
            } else {
                completedChunks = that.wrapper.find("li.k-item:gt(-" + (completedChunksCount + 1) + ")");
            }

            that.wrapper.find("." + KCOMPLETEDCHUNK)
                        .removeClass(KCOMPLETEDCHUNK)
                        .addClass(KUPCOMINGCHUNK);

            completedChunks.removeClass(KUPCOMINGCHUNK)
                           .addClass(KCOMPLETEDCHUNK);
        },

        _updateProgressWrapper: function (percentage) {
            var that = this;
            var options = that.options;
            var progressWrapper = that.wrapper.find("." + KPROGRESSWRAPPER);
            var animationDuration = that._isStarted ? that._animation.duration : 0;
            var animationCssOptions = {};

            if (progressWrapper.length === 0) {
                that._addRegularProgressWrapper();
            }

            animationCssOptions[that._progressProperty] = percentage + "%";
            that.progressWrapper.animate(animationCssOptions, {
                duration: animationDuration,
                start: proxy(that._onProgressAnimateStart, that),
                progress: proxy(that._onProgressAnimate, that),
                complete: proxy(that._onProgressAnimateComplete, that, options.value),
                always: proxy(that._onProgressUpdateAlways, that, options.value)
            });
        },

        _onProgressAnimateStart: function () {
            this.progressWrapper.show();
        },

        _onProgressAnimate: function (e) {
            var that = this;
            var options = that.options;
            var progressInPercent = parseFloat(e.elem.style[that._progressProperty], 10);
            var progressStatusWrapSize;

            if (options.showStatus) {
                progressStatusWrapSize = 10000 / parseFloat(that.progressWrapper[0].style[that._progressProperty]);

                that.progressWrapper.find(".k-progress-status-wrap").css(that._progressProperty, progressStatusWrapSize + "%");
            }

            if (options.type !== PROGRESSTYPE.CHUNK && progressInPercent <= 98) {
                that.progressWrapper.removeClass(KPROGRESSBARCOMPLETE);
            }
        },

        _onProgressAnimateComplete: function (currentValue) {
            var that = this;
            var options = that.options;
            var progressWrapperSize = parseFloat(that.progressWrapper[0].style[that._progressProperty]);

            if (options.type !== PROGRESSTYPE.CHUNK && progressWrapperSize > 98) {
                that.progressWrapper.addClass(KPROGRESSBARCOMPLETE);
            }

            if (options.showStatus) {
                if (options.type === PROGRESSTYPE.VALUE) {
                    that.progressStatus.text(currentValue);
                } else {
                    that.progressStatus.text(math.floor(that._calculatePercentage(currentValue)) + "%");
                }
            }

            if (currentValue === options.min) {
                that.progressWrapper.hide();
            }
        },

        _onProgressUpdateAlways: function (currentValue) {
            var that = this;
            var options = that.options;

            if (that._isStarted) {
                that.trigger(CHANGE, { value: currentValue });
            }

            if (currentValue === options.max && that._isStarted) {
                that.trigger(COMPLETE, { value: options.max });
            }
        },

        enable: function (enable) {
            var that = this;
            var options = that.options;

            options.enable = typeof (enable) === "undefined" ? true : enable;
            that.wrapper.toggleClass(KSTATEDISABLED, !options.enable);
        },

        destroy: function () {
            var that = this;

            Widget.fn.destroy.call(that);
        },

        _addChunkProgressWrapper: function () {
            var that = this;
            var options = that.options;
            var container = that.wrapper;
            var chunkSize = HUNDREDPERCENT / options.chunkCount;
            var html = "";

            if (options.chunkCount <= 1) {
                options.chunkCount = DEFAULTCHUNKCOUNT;
            }

            html += "<ul class='k-reset'>";
            for (var i = options.chunkCount - 1; i >= 0; i--) {
                html += "<li class='k-item k-state-default'></li>";
            }
            html += "</ul>";

            container.append(html).find(".k-item").css(that._progressProperty, chunkSize + "%")
                     .first().addClass("k-first")
                     .end()
                     .last().addClass("k-last");

            that._normalizeChunkSize();
        },

        _normalizeChunkSize: function () {
            var that = this;
            var options = that.options;
            var lastChunk = that.wrapper.find(".k-item:last");
            var currentSize = parseFloat(lastChunk[0].style[that._progressProperty]);
            var difference = HUNDREDPERCENT - (options.chunkCount * currentSize);

            if (difference > 0) {
                lastChunk.css(that._progressProperty, (currentSize + difference) + "%");
            }
        },

        _addRegularProgressWrapper: function () {
            var that = this;

            that.progressWrapper = $("<div class='" + KPROGRESSWRAPPER + "'></div>").appendTo(that.wrapper);

            if (that.options.showStatus) {
                that.progressWrapper.append(templates.progressStatus);

                that.progressStatus = that.wrapper.find("." + KPROGRESSSTATUS);
            }
        },

        _calculateChunkSize: function () {
            var that = this;
            var chunkCount = that.options.chunkCount;
            var chunkContainer = that.wrapper.find("ul.k-reset");

            return (parseInt(chunkContainer.css(that._progressProperty), 10) - (chunkCount - 1)) / chunkCount;
        },

        _calculatePercentage: function (currentValue) {
            var that = this;
            var options = that.options;
            var value = (currentValue !== undefined) ? currentValue : options.value;
            var min = options.min;
            var max = options.max;
            that._onePercent = math.abs((max - min) / 100);

            return math.abs((value - min) / that._onePercent);
        },

        _progressAnimation: function () {
            var that = this;
            var options = that.options;
            var animation = options.animation;

            if (animation === false) {
                that._animation = { duration: 0 };
            } else {
                that._animation = extend({
                    duration: DEFAULTANIMATIONDURATION
                }, options.animation);
            }
        }
    });

    kendo.ui.plugin(ProgressBar);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        keys = kendo.keys,
        map = $.map,
        each = $.each,
        trim = $.trim,
        extend = $.extend,
        template = kendo.template,
        Widget = ui.Widget,
        excludedNodesRegExp = /^(a|div)$/i,
        NS = ".kendoTabStrip",
        IMG = "img",
        HREF = "href",
        PREV = "prev",
        SHOW = "show",
        LINK = "k-link",
        LAST = "k-last",
        CLICK = "click",
        ERROR = "error",
        EMPTY = ":empty",
        IMAGE = "k-image",
        FIRST = "k-first",
        SELECT = "select",
        ACTIVATE = "activate",
        CONTENT = "k-content",
        CONTENTURL = "contentUrl",
        MOUSEENTER = "mouseenter",
        MOUSELEAVE = "mouseleave",
        CONTENTLOAD = "contentLoad",
        DISABLEDSTATE = "k-state-disabled",
        DEFAULTSTATE = "k-state-default",
        ACTIVESTATE = "k-state-active",
        FOCUSEDSTATE = "k-state-focused",
        HOVERSTATE = "k-state-hover",
        TABONTOP = "k-tab-on-top",
        NAVIGATABLEITEMS = ".k-item:not(." + DISABLEDSTATE + ")",
        HOVERABLEITEMS = ".k-tabstrip-items > " + NAVIGATABLEITEMS + ":not(." + ACTIVESTATE + ")",

        templates = {
            content: template(
                "<div class='k-content'#= contentAttributes(data) # role='tabpanel'>#= content(item) #</div>"
            ),
            itemWrapper: template(
                "<#= tag(item) # class='k-link'#= contentUrl(item) ##= textAttributes(item) #>" +
                    "#= image(item) ##= sprite(item) ##= text(item) #" +
                "</#= tag(item) #>"
            ),
            item: template(
                "<li class='#= wrapperCssClass(group, item) #' role='tab' #=item.active ? \"aria-selected='true'\" : ''#>" +
                    "#= itemWrapper(data) #" +
                "</li>"
            ),
            image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
            sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
            empty: template("")
        },

        rendering = {
            wrapperCssClass: function (group, item) {
                var result = "k-item",
                    index = item.index;

                if (item.enabled === false) {
                    result += " k-state-disabled";
                } else {
                    result += " k-state-default";
                }

                if (index === 0) {
                    result += " k-first";
                }

                if (index == group.length - 1) {
                    result += " k-last";
                }

                return result;
            },
            textAttributes: function (item) {
                return item.url ? " href='" + item.url + "'" : "";
            },
            text: function (item) {
                return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
            },
            tag: function (item) {
                return item.url ? "a" : "span";
            },
            contentAttributes: function (content) {
                return content.active !== true ? " style='display:none' aria-hidden='true' aria-expanded='false'" : "";
            },
            content: function (item) {
                return item.content ? item.content : item.contentUrl ? "" : "&nbsp;";
            },
            contentUrl: function (item) {
                return item.contentUrl ? kendo.attr("content-url") + '="' + item.contentUrl + '"' : "";
            }
        };

    function updateTabClasses(tabs) {
        tabs.children(IMG)
            .addClass(IMAGE);

        tabs.children("a")
            .addClass(LINK)
            .children(IMG)
            .addClass(IMAGE);

        tabs.filter(":not([disabled]):not([class*=k-state-disabled])")
            .addClass(DEFAULTSTATE);

        tabs.filter("li[disabled]")
            .addClass(DISABLEDSTATE)
            .removeAttr("disabled");

        tabs.filter(":not([class*=k-state])")
            .children("a")
            .filter(":focus")
            .parent()
            .addClass(ACTIVESTATE + " " + TABONTOP);

        tabs.attr("role", "tab");
        tabs.filter("." + ACTIVESTATE)
            .attr("aria-selected", true);


        tabs.each(function () {
            var item = $(this);

            if (!item.children("." + LINK).length) {
                item
                    .contents()      // exclude groups, real links, templates and empty text nodes
                    .filter(function () { return (!this.nodeName.match(excludedNodesRegExp) && !(this.nodeType == 3 && !trim(this.nodeValue))); })
                    .wrapAll("<a class='" + LINK + "'/>");
            }
        });

    }

    function updateFirstLast(tabGroup) {
        var tabs = tabGroup.children(".k-item");

        tabs.filter(".k-first:not(:first-child)").removeClass(FIRST);
        tabs.filter(".k-last:not(:last-child)").removeClass(LAST);
        tabs.filter(":first-child").addClass(FIRST);
        tabs.filter(":last-child").addClass(LAST);
    }

    var TabStrip = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            that._animations(that.options);

            options = that.options;

            that._wrapper();

            that._isRtl = kendo.support.isRtl(that.wrapper);

            that._tabindex();

            that._updateClasses();

            that._dataSource();

            if (options.dataSource) {
                that.dataSource.fetch();
            }

            if (that.options.contentUrls) {
                that.wrapper.find(".k-tabstrip-items > .k-item")
                    .each(function (index, item) {
                        $(item).find(">." + LINK).data(CONTENTURL, that.options.contentUrls[index]);
                    });
            }

            that.wrapper
                .on(MOUSEENTER + NS + " " + MOUSELEAVE + NS, HOVERABLEITEMS, that._toggleHover)
                .on("focus" + NS, $.proxy(that._active, that))
                .on("blur" + NS, function () { that._current(null); });

            that._keyDownProxy = $.proxy(that._keydown, that);

            if (options.navigatable) {
                that.wrapper.on("keydown" + NS, that._keyDownProxy);
            }

            that.wrapper.children(".k-tabstrip-items")
                .on(CLICK + NS, ".k-state-disabled .k-link", false)
                .on(CLICK + NS, " > " + NAVIGATABLEITEMS, function (e) {
                    var wr = that.wrapper[0];
                    if (wr !== document.activeElement) {
                        var msie = kendo.support.browser.msie;
                        if (msie) {
                            try {
                                // does not scroll to the active element
                                wr.setActive();
                            } catch (j) {
                                wr.focus();
                            }
                        } else {
                            wr.focus();
                        }
                    }

                    if (that._click($(e.currentTarget))) {
                        e.preventDefault();
                    }
                });

            var selectedItems = that.tabGroup.children("li." + ACTIVESTATE),
                content = that.contentHolder(selectedItems.index());

            if (selectedItems[0] && content.length > 0 && content[0].childNodes.length === 0) {
                that.activateTab(selectedItems.eq(0));
            }

            that.element.attr("role", "tablist");

            if (that.element[0].id) {
                that._ariaId = that.element[0].id + "_ts_active";
            }

            kendo.notify(that);
        },

        _active: function () {
            var item = this.tabGroup.children().filter("." + ACTIVESTATE);

            item = item[0] ? item : this._endItem("first");
            if (item[0]) {
                this._current(item);
            }
        },

        _endItem: function (action) {
            return this.tabGroup.children(NAVIGATABLEITEMS)[action]();
        },

        _item: function (item, action) {
            var endItem;
            if (action === PREV) {
                endItem = "last";
            } else {
                endItem = "first";
            }

            if (!item) {
                return this._endItem(endItem);
            }

            item = item[action]();

            if (!item[0]) {
                item = this._endItem(endItem);
            }

            if (item.hasClass(DISABLEDSTATE)) {
                item = this._item(item, action);
            }

            return item;
        },

        _current: function (candidate) {
            var that = this,
                focused = that._focused,
                id = that._ariaId;

            if (candidate === undefined) {
                return focused;
            }

            if (focused) {
                if (focused[0].id === id) {
                    focused.removeAttr("id");
                }
                focused.removeClass(FOCUSEDSTATE);
            }

            if (candidate) {
                if (!candidate.hasClass(ACTIVESTATE)) {
                    candidate.addClass(FOCUSEDSTATE);
                }

                that.element.removeAttr("aria-activedescendant");

                id = candidate[0].id || id;

                if (id) {
                    candidate.attr("id", id);
                    that.element.attr("aria-activedescendant", id);
                }
            }

            that._focused = candidate;
        },

        _keydown: function (e) {
            var that = this,
                key = e.keyCode,
                current = that._current(),
                rtl = that._isRtl,
                action;

            if (e.target != e.currentTarget) {
                return;
            }

            if (key == keys.DOWN || key == keys.RIGHT) {
                action = rtl ? PREV : "next";
            } else if (key == keys.UP || key == keys.LEFT) {
                action = rtl ? "next" : PREV;
            } else if (key == keys.ENTER || key == keys.SPACEBAR) {
                that._click(current);
                e.preventDefault();
            } else if (key == keys.HOME) {
                that._click(that._endItem("first"));
                e.preventDefault();
                return;
            } else if (key == keys.END) {
                that._click(that._endItem("last"));
                e.preventDefault();
                return;
            }

            if (action) {
                that._click(that._item(current, action));
                e.preventDefault();
            }
        },

        _dataSource: function () {
            var that = this;

            if (that.dataSource && that._refreshHandler) {
                that.dataSource.unbind("change", that._refreshHandler);
            } else {
                that._refreshHandler = $.proxy(that.refresh, that);
            }

            that.dataSource = kendo.data.DataSource.create(that.options.dataSource)
                                .bind("change", that._refreshHandler);
        },

        setDataSource: function (dataSource) {
            this.options.dataSource = dataSource;
            this._dataSource();
            dataSource.fetch();
        },

        _animations: function (options) {
            if (options && ("animation" in options) && !options.animation) {
                options.animation = { open: { effects: {} }, close: { effects: {}} }; // No animation
            }
        },

        refresh: function (e) {
            var that = this,
                options = that.options,
                text = kendo.getter(options.dataTextField),
                content = kendo.getter(options.dataContentField),
                contentUrl = kendo.getter(options.dataContentUrlField),
                image = kendo.getter(options.dataImageUrlField),
                url = kendo.getter(options.dataUrlField),
                sprite = kendo.getter(options.dataSpriteCssClass),
                idx,
                tabs = [],
                tab,
                action,
                view = that.dataSource.view(),
                length;


            e = e || {};
            action = e.action;

            if (action) {
                view = e.items;
            }

            for (idx = 0, length = view.length; idx < length; idx++) {
                tab = {
                    text: text(view[idx])
                };

                if (options.dataContentField) {
                    tab.content = content(view[idx]);
                }

                if (options.dataContentUrlField) {
                    tab.contentUrl = contentUrl(view[idx]);
                }

                if (options.dataUrlField) {
                    tab.url = url(view[idx]);
                }

                if (options.dataImageUrlField) {
                    tab.imageUrl = image(view[idx]);
                }

                if (options.dataSpriteCssClass) {
                    tab.spriteCssClass = sprite(view[idx]);
                }

                tabs[idx] = tab;
            }

            if (e.action == "add") {
                if (e.index < that.tabGroup.children().length) {
                    that.insertBefore(tabs, that.tabGroup.children().eq(e.index));
                } else {
                    that.append(tabs);
                }
            } else if (e.action == "remove") {
                for (idx = 0; idx < view.length; idx++) {
                    that.remove(e.index);
                }
            } else if (e.action == "itemchange") {
                idx = that.dataSource.view().indexOf(view[0]);
                if (e.field === options.dataTextField) {
                    that.tabGroup.children().eq(idx).find(".k-link").text(view[0].get(e.field));
                }
            } else {
                that.trigger("dataBinding");
                that.remove("li");
                that.append(tabs);
                that.trigger("dataBound");
            }
        },

        value: function (value) {
            var that = this;

            if (value !== undefined) {
                if (value != that.value()) {
                    that.tabGroup.children().each(function () {
                        if ($.trim($(this).text()) == value) {
                            that.select(this);
                        }
                    });
                }
            } else {
                return that.select().text();
            }
        },

        items: function () {
            return this.tabGroup[0].children;
        },

        setOptions: function (options) {
            var that = this,
                animation = that.options.animation;

            that._animations(options);

            options.animation = extend(true, animation, options.animation);

            if (options.navigatable) {
                that.wrapper.on("keydown" + NS, that._keyDownProxy);
            } else {
                that.wrapper.off("keydown" + NS, that._keyDownProxy);
            }

            Widget.fn.setOptions.call(that, options);
        },

        events: [
            SELECT,
            ACTIVATE,
            SHOW,
            ERROR,
            CONTENTLOAD,
            "change",
            "dataBinding",
            "dataBound"
        ],

        options: {
            name: "TabStrip",
            dataTextField: "",
            dataContentField: "",
            dataImageUrlField: "",
            dataUrlField: "",
            dataSpriteCssClass: "",
            dataContentUrlField: "",
            animation: {
                open: {
                    effects: "expand:vertical fadeIn",
                    duration: 200
                },
                close: { // if close animation effects are defined, they will be used instead of open.reverse
                    duration: 200
                }
            },
            collapsible: false,
            navigatable: true,
            contentUrls: false
        },

        destroy: function () {
            var that = this;

            Widget.fn.destroy.call(that);

            if (that._refreshHandler) {
                that.dataSource.unbind("change", that._refreshHandler);
            }

            that.wrapper.off(NS);
            that.wrapper.children(".k-tabstrip-items").off(NS);

            that.scrollWrap.children(".k-tabstrip").unwrap();

            kendo.destroy(that.wrapper);
        },

        select: function (element) {
            var that = this;

            if (arguments.length === 0) {
                return that.tabGroup.children("li." + ACTIVESTATE);
            }

            if (!isNaN(element)) {
                element = that.tabGroup.children().get(element);
            }

            element = that.tabGroup.find(element);
            $(element).each(function (index, item) {
                item = $(item);
                if (!item.hasClass(ACTIVESTATE) && !that.trigger(SELECT, { item: item[0], contentElement: that.contentHolder(item.index())[0] })) {
                    that.activateTab(item);
                }
            });

            return that;
        },

        enable: function (element, state) {
            this._toggleDisabled(element, state !== false);

            return this;
        },

        disable: function (element) {
            this._toggleDisabled(element, false);

            return this;
        },

        reload: function (element) {
            element = this.tabGroup.find(element);
            var that = this;

            element.each(function () {
                var item = $(this),
                    contentUrl = item.find("." + LINK).data(CONTENTURL),
                    content = that.contentHolder(item.index());

                if (contentUrl) {
                    that.ajaxRequest(item, content, null, contentUrl);
                }
            });

            return that;
        },

        append: function (tab) {
            var that = this,
                inserted = that._create(tab);

            each(inserted.tabs, function (idx) {
                that.tabGroup.append(this);
                that.wrapper.append(inserted.contents[idx]);
            });

            updateFirstLast(that.tabGroup);
            that._updateContentElements();

            return that;
        },

        insertBefore: function (tab, referenceTab) {
            referenceTab = this.tabGroup.find(referenceTab);

            var that = this,
                inserted = that._create(tab),
                referenceContent = $(that.contentElement(referenceTab.index()));

            each(inserted.tabs, function (idx) {
                referenceTab.before(this);
                referenceContent.before(inserted.contents[idx]);
            });

            updateFirstLast(that.tabGroup);
            that._updateContentElements();

            return that;
        },

        insertAfter: function (tab, referenceTab) {
            referenceTab = this.tabGroup.find(referenceTab);

            var that = this,
                inserted = that._create(tab),
                referenceContent = $(that.contentElement(referenceTab.index()));

            each(inserted.tabs, function (idx) {
                referenceTab.after(this);
                referenceContent.after(inserted.contents[idx]);
            });

            updateFirstLast(that.tabGroup);
            that._updateContentElements();

            return that;
        },

        remove: function (elements) {
            var that = this;
            var type = typeof elements;
            var contents;

            if (type === "string") {
                elements = that.tabGroup.find(elements);
            } else if (type === "number") {
                elements = that.tabGroup.children().eq(elements);
            }

            contents = elements.map(function () {
                var content = that.contentElement($(this).index());
                kendo.destroy(content);
                return content;
            });

            elements.remove();
            contents.remove();

            that._updateContentElements();

            return that;
        },

        _create: function (tab) {
            var plain = $.isPlainObject(tab),
                that = this, tabs, contents, content;

            if (plain || $.isArray(tab)) {
                tab = $.isArray(tab) ? tab : [tab];

                tabs = map(tab, function (value, idx) {
                    return $(TabStrip.renderItem({
                        group: that.tabGroup,
                        item: extend(value, { index: idx })
                    }));
                });

                contents = map(tab, function (value, idx) {
                    if (typeof value.content == "string" || value.contentUrl) {
                        return $(TabStrip.renderContent({
                            item: extend(value, { index: idx })
                        }));
                    }
                });
            } else {
                if (typeof tab == "string" && tab[0] != "<") {
                    tabs = that.element.find(tab);
                } else {
                    tabs = $(tab);
                }
                contents = $();
                tabs.each(function () {
                    content = $("<div class='" + CONTENT + "'/>");
                    if (/k-tabstrip-items/.test(this.parentNode.className)) {
                        var index = parseInt(this.getAttribute("aria-controls").replace(/^.*-/, ""), 10) - 1;
                        content = $(that.contentElement(index));
                    }
                    contents = contents.add(content);
                });

                updateTabClasses(tabs);
            }

            return { tabs: tabs, contents: contents };
        },

        _toggleDisabled: function (element, enable) {
            element = this.tabGroup.find(element);
            element.each(function () {
                $(this)
                    .toggleClass(DEFAULTSTATE, enable)
                    .toggleClass(DISABLEDSTATE, !enable);
            });
        },

        _updateClasses: function () {
            var that = this,
                tabs, activeItem, activeTab;

            that.wrapper.addClass("k-widget k-header k-tabstrip");

            that.tabGroup = that.wrapper.children("ul").addClass("k-tabstrip-items k-reset");

            if (!that.tabGroup[0]) {
                that.tabGroup = $("<ul class='k-tabstrip-items k-reset'/>").appendTo(that.wrapper);
            }

            tabs = that.tabGroup.find("li").addClass("k-item");

            if (tabs.length) {
                activeItem = tabs.filter("." + ACTIVESTATE).index();
                activeTab = activeItem >= 0 ? activeItem : undefined;

                that.tabGroup // Remove empty text nodes
                    .contents()
                    .filter(function () { return (this.nodeType == 3 && !trim(this.nodeValue)); })
                    .remove();
            }

            if (activeItem >= 0) {
                tabs.eq(activeItem).addClass(TABONTOP);
            }

            that.contentElements = that.wrapper.children("div");

            that.contentElements
                .addClass(CONTENT)
                .eq(activeTab)
                .addClass(ACTIVESTATE)
                .css({ display: "block" });

            if (tabs.length) {
                updateTabClasses(tabs);

                updateFirstLast(that.tabGroup);
                that._updateContentElements();
            }
        },

        _updateContentElements: function () {
            var that = this,
                contentUrls = that.options.contentUrls || [],
                items = that.tabGroup.find(".k-item"),
                tabStripID = (that.element.attr("id") || kendo.guid()) + "-",
                contentElements = that.wrapper.children("div");

            if (contentElements.length && (items.length > contentElements.length)) {
                contentElements.each(function (idx) {
                    var currentIndex = parseInt(this.id.replace(tabStripID, ""), 10),
                        item = items.filter("[aria-controls=" + tabStripID + currentIndex + "]"),
                        id = tabStripID + (idx + 1);

                    item.data("aria", id);
                    this.setAttribute("id", id);
                });

                items.each(function () {
                    var item = $(this);

                    this.setAttribute("aria-controls", item.data("aria"));
                    item.removeData("aria");
                });
            } else {
                items.each(function (idx) {
                    var currentContent = contentElements.eq(idx),
                        id = tabStripID + (idx + 1);

                    this.setAttribute("aria-controls", id);

                    if (!currentContent.length && contentUrls[idx]) {
                        $("<div class='" + CONTENT + "'/>").appendTo(that.wrapper).attr("id", id);
                    } else {
                        currentContent.attr("id", id);

                        if (!$(this).children(".k-loading")[0] && !contentUrls[idx]) {
                            $("<span class='k-loading k-complete'/>").prependTo(this);
                        }
                    }
                    currentContent.attr("role", "tabpanel");
                    currentContent.filter(":not(." + ACTIVESTATE + ")").attr("aria-hidden", true).attr("aria-expanded", false);
                    currentContent.filter("." + ACTIVESTATE).attr("aria-expanded", true);
                });
            }

            that.contentElements = that.contentAnimators = that.wrapper.children("div"); // refresh the contents

            that.tabsHeight = that.tabGroup.outerHeight() +
                              parseInt(that.wrapper.css("border-top-width"), 10) +
                              parseInt(that.wrapper.css("border-bottom-width"), 10);

            if (kendo.kineticScrollNeeded && kendo.mobile.ui.Scroller) {
                kendo.touchScroller(that.contentElements);
                that.contentElements = that.contentElements.children(".km-scroll-container");
            }
        },

        _wrapper: function () {
            var that = this;

            if (that.element.is("ul")) {
                that.wrapper = that.element.wrapAll("<div />").parent();
            } else {
                that.wrapper = that.element;
            }

            that.scrollWrap = that.wrapper.parent(".k-tabstrip-wrapper");

            if (!that.scrollWrap[0]) {
                that.scrollWrap = that.wrapper.wrapAll("<div class='k-tabstrip-wrapper' />").parent();
            }
        },

        _sizeScrollWrap: function (element) {
            this.scrollWrap.css("height", Math.floor(element.outerHeight(true)) + this.tabsHeight).css("height");
        },

        _toggleHover: function (e) {
            $(e.currentTarget).toggleClass(HOVERSTATE, e.type == MOUSEENTER);
        },

        _click: function (item) {
            var that = this,
                link = item.find("." + LINK),
                href = link.attr(HREF),
                collapse = that.options.collapsible,
                contentHolder = that.contentHolder(item.index()),
                prevent, isAnchor;

            if (item.closest(".k-widget")[0] != that.wrapper[0]) {
                return;
            }

            if (item.is("." + DISABLEDSTATE + (!collapse ? ",." + ACTIVESTATE : ""))) {
                return true;
            }

            isAnchor = link.data(CONTENTURL) || (href && (href.charAt(href.length - 1) == "#" || href.indexOf("#" + that.element[0].id + "-") != -1));
            prevent = !href || isAnchor;

            if (that.tabGroup.children("[data-animating]").length) {
                return prevent;
            }

            if (that.trigger(SELECT, { item: item[0], contentElement: contentHolder[0] })) {
                return true;
            }

            if (prevent === false) {
                return;
            }

            if (collapse && item.is("." + ACTIVESTATE)) {
                that.deactivateTab(item);
                return true;
            }

            if (that.activateTab(item)) {
                prevent = true;
            }

            return prevent;
        },

        deactivateTab: function (item) {
            var that = this,
                animationSettings = that.options.animation,
                animation = animationSettings.open,
                close = extend({}, animationSettings.close),
                hasCloseAnimation = close && "effects" in close;
            item = that.tabGroup.find(item);

            close = extend(hasCloseAnimation ? close : extend({ reverse: true }, animation), { hide: true });

            if (kendo.size(animation.effects)) {
                item.kendoAddClass(DEFAULTSTATE, { duration: animation.duration });
                item.kendoRemoveClass(ACTIVESTATE, { duration: animation.duration });
            } else {
                item.addClass(DEFAULTSTATE);
                item.removeClass(ACTIVESTATE);
            }

            item.removeAttr("aria-selected");

            that.contentAnimators
                    .filter("." + ACTIVESTATE)
                    .kendoStop(true, true)
                    .kendoAnimate(close)
                    .removeClass(ACTIVESTATE)
                    .attr("aria-hidden", true);
        },

        activateTab: function (item) {
            if (this.tabGroup.children("[data-animating]").length) { return; }

            item = this.tabGroup.find(item);

            var that = this,
                animationSettings = that.options.animation,
                animation = animationSettings.open,
                close = extend({}, animationSettings.close),
                hasCloseAnimation = close && "effects" in close,
                neighbours = item.parent().children(),
                oldTab = neighbours.filter("." + ACTIVESTATE),
                itemIndex = neighbours.index(item);

            close = extend(hasCloseAnimation ? close : extend({ reverse: true }, animation), { hide: true });
            // deactivate previously active tab
            if (kendo.size(animation.effects)) {
                oldTab.kendoRemoveClass(ACTIVESTATE, { duration: close.duration });
                item.kendoRemoveClass(HOVERSTATE, { duration: close.duration });
            } else {
                oldTab.removeClass(ACTIVESTATE);
                item.removeClass(HOVERSTATE);
            }

            // handle content elements
            var contentAnimators = that.contentAnimators;

            if (that.inRequest) {
                that.xhr.abort();
                that.inRequest = false;
            }

            if (contentAnimators.length === 0) {
                oldTab.removeClass(TABONTOP);
                item.addClass(TABONTOP) // change these directly to bring the tab on top.
                    .css("z-index");

                item.addClass(ACTIVESTATE);
                that._current(item);

                that.trigger("change");

                return false;
            }

            var visibleContents = contentAnimators.filter("." + ACTIVESTATE),
                contentHolder = that.contentHolder(itemIndex),
                contentElement = contentHolder.closest(".k-content");

            that.tabsHeight = that.tabGroup.outerHeight() +
                              parseInt(that.wrapper.css("border-top-width"), 10) +
                              parseInt(that.wrapper.css("border-bottom-width"), 10);

            that._sizeScrollWrap(visibleContents);

            if (contentHolder.length === 0) {
                visibleContents
                    .removeClass(ACTIVESTATE)
                    .attr("aria-hidden", true)
                    .kendoStop(true, true)
                    .kendoAnimate(close);
                return false;
            }

            item.attr("data-animating", true);

            var isAjaxContent = (item.children("." + LINK).data(CONTENTURL) || false) && contentHolder.is(EMPTY),
                showContentElement = function () {
                    oldTab.removeClass(TABONTOP);
                    item.addClass(TABONTOP) // change these directly to bring the tab on top.
                        .css("z-index");

                    if (kendo.size(animation.effects)) {
                        oldTab.kendoAddClass(DEFAULTSTATE, { duration: animation.duration });
                        item.kendoAddClass(ACTIVESTATE, { duration: animation.duration });
                    } else {
                        oldTab.addClass(DEFAULTSTATE);
                        item.addClass(ACTIVESTATE);
                    }
                    oldTab.removeAttr("aria-selected");
                    item.attr("aria-selected", true);

                    that._current(item);

                    that._sizeScrollWrap(contentElement);

                    contentElement
                        .addClass(ACTIVESTATE)
                        .removeAttr("aria-hidden")
                        .kendoStop(true, true)
                        .attr("aria-expanded", true)
                        .kendoAnimate(extend({ init: function () {
                            that.trigger(SHOW, { item: item[0], contentElement: contentHolder[0] });
                            kendo.resize(contentHolder);
                        } 
                        }, animation, {
                            complete: function () {
                                item.removeAttr("data-animating");

                                that.trigger(ACTIVATE, { item: item[0], contentElement: contentHolder[0] });
                                kendo.resize(contentHolder);

                                that.scrollWrap.css("height", "").css("height");
                            }
                        }));
                },
                showContent = function () {
                    if (!isAjaxContent) {
                        showContentElement();
                        that.trigger("change");
                    } else {
                        item.removeAttr("data-animating");
                        that.ajaxRequest(item, contentHolder, function () {
                            item.attr("data-animating", true);
                            showContentElement();
                            that.trigger("change");
                        });
                    }
                };

            visibleContents
                    .removeClass(ACTIVESTATE);

            visibleContents.attr("aria-hidden", true);
            visibleContents.attr("aria-expanded", false);

            if (visibleContents.length) {
                visibleContents
                    .kendoStop(true, true)
                    .kendoAnimate(extend({
                        complete: showContent
                    }, close));
            } else {
                showContent();
            }

            return true;
        },

        contentElement: function (itemIndex) {
            if (isNaN(itemIndex - 0)) {
                return undefined;
            }

            var contentElements = this.contentElements && this.contentElements[0] && !kendo.kineticScrollNeeded ? this.contentElements : this.contentAnimators;

            itemIndex = contentElements && itemIndex < 0 ? contentElements.length + itemIndex : itemIndex;

            var idTest = new RegExp("-" + (itemIndex + 1) + "$");

            if (contentElements) {
                for (var i = 0, len = contentElements.length; i < len; i++) {
                    if (idTest.test(contentElements.eq(i).closest(".k-content")[0].id)) {
                        return contentElements[i];
                    }
                }
            }

            return undefined;
        },

        contentHolder: function (itemIndex) {
            var contentElement = $(this.contentElement(itemIndex)),
                scrollContainer = contentElement.children(".km-scroll-container");

            return kendo.support.touch && scrollContainer[0] ? scrollContainer : contentElement;
        },

        ajaxRequest: function (element, content, complete, url) {
            element = this.tabGroup.find(element);

            var that = this,
                xhr = $.ajaxSettings.xhr,
                link = element.find("." + LINK),
                data = {},
                halfWidth = element.width() / 2,
                fakeProgress = false,
                statusIcon = element.find(".k-loading").removeClass("k-complete");

            if (!statusIcon[0]) {
                statusIcon = $("<span class='k-loading'/>").prependTo(element);
            }

            var endState = halfWidth * 2 - statusIcon.width();

            var oldProgressAnimation = function () {
                statusIcon.animate({ marginLeft: (parseInt(statusIcon.css("marginLeft"), 10) || 0) < halfWidth ? endState : 0 }, 500, oldProgressAnimation);
            };

            if (kendo.support.browser.msie && kendo.support.browser.version < 10) {
                setTimeout(oldProgressAnimation, 40);
            }

            url = url || link.data(CONTENTURL) || link.attr(HREF);
            that.inRequest = true;

            that.xhr = $.ajax({
                type: "GET",
                cache: false,
                url: url,
                dataType: "html",
                data: data,
                xhr: function () {
                    var current = this,
                        request = xhr(),
                        event = current.progressUpload ? "progressUpload" : current.progress ? "progress" : false;

                    if (request) {
                        $.each([request, request.upload], function () {
                            if (this.addEventListener) {
                                this.addEventListener("progress", function (evt) {
                                    if (event) {
                                        current[event](evt);
                                    }
                                }, false);
                            }
                        });
                    }

                    current.noProgress = !(window.XMLHttpRequest && ('upload' in new XMLHttpRequest()));
                    return request;
                },

                progress: function (evt) {
                    if (evt.lengthComputable) {
                        var percent = parseInt((evt.loaded / evt.total * 100), 10) + "%";
                        statusIcon
                            .stop(true)
                            .addClass("k-progress")
                            .css({
                                "width": percent,
                                "marginLeft": 0
                            });
                    }
                },

                error: function (xhr, status) {
                    if (that.trigger("error", { xhr: xhr, status: status })) {
                        this.complete();
                    }
                },

                stopProgress: function () {
                    clearInterval(fakeProgress);
                    statusIcon
                        .stop(true)
                        .addClass("k-progress")
                        [0].style.cssText = "";
                },

                complete: function (xhr) {
                    that.inRequest = false;
                    if (this.noProgress) {
                        setTimeout(this.stopProgress, 500);
                    } else {
                        this.stopProgress();
                    }

                    if (xhr.statusText == "abort") {
                        statusIcon.remove();
                    }
                },

                success: function (data) {
                    statusIcon.addClass("k-complete");
                    try {
                        var current = this,
                            loaded = 10;

                        if (current.noProgress) {
                            statusIcon.width(loaded + "%");
                            fakeProgress = setInterval(function () {
                                current.progress({ lengthComputable: true, loaded: Math.min(loaded, 100), total: 100 });
                                loaded += 10;
                            }, 40);
                        }

                        that.angular("cleanup", function () { return { elements: content.get() }; });
                        kendo.destroy(content);
                        content.html(data);
                    } catch (e) {
                        var console = window.console;

                        if (console && console.error) {
                            console.error(e.name + ": " + e.message + " in " + url);
                        }
                        this.error(this.xhr, "error");
                    }

                    if (complete) {
                        complete.call(that, content);
                    }

                    that.angular("compile", function () { return { elements: content.get() }; });

                    that.trigger(CONTENTLOAD, { item: element[0], contentElement: content[0] });
                }
            });
        }
    });

    // client-side rendering
    extend(TabStrip, {
        renderItem: function (options) {
            options = extend({ tabStrip: {}, group: {} }, options);

            var empty = templates.empty,
                item = options.item;

            return templates.item(extend(options, {
                image: item.imageUrl ? templates.image : empty,
                sprite: item.spriteCssClass ? templates.sprite : empty,
                itemWrapper: templates.itemWrapper
            }, rendering));
        },

        renderContent: function (options) {
            return templates.content(extend(options, rendering));
        }
    });

    kendo.ui.plugin(TabStrip);

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        keys = kendo.keys,
        parse = kendo.parseDate,
        activeElement = kendo._activeElement,
        extractFormat = kendo._extractFormat,
        support = kendo.support,
        browser = support.browser,
        ui = kendo.ui,
        Widget = ui.Widget,
        OPEN = "open",
        CLOSE = "close",
        CHANGE = "change",
        ns = ".kendoTimePicker",
        CLICK = "click" + ns,
        DEFAULT = "k-state-default",
        DISABLED = "disabled",
        READONLY = "readonly",
        LI = "li",
        SPAN = "<span/>",
        FOCUSED = "k-state-focused",
        HOVER = "k-state-hover",
        HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns,
        MOUSEDOWN = "mousedown" + ns,
        MS_PER_MINUTE = 60000,
        MS_PER_DAY = 86400000,
        SELECTED = "k-state-selected",
        STATEDISABLED = "k-state-disabled",
        ARIA_SELECTED = "aria-selected",
        ARIA_EXPANDED = "aria-expanded",
        ARIA_HIDDEN = "aria-hidden",
        ARIA_DISABLED = "aria-disabled",
        ARIA_READONLY = "aria-readonly",
        ARIA_ACTIVEDESCENDANT = "aria-activedescendant",
        ID = "id",
        isArray = $.isArray,
        extend = $.extend,
        proxy = $.proxy,
        DATE = Date,
        TODAY = new DATE();

    TODAY = new DATE(TODAY.getFullYear(), TODAY.getMonth(), TODAY.getDate(), 0, 0, 0);

    var TimeView = function (options) {
        var that = this,
            id = options.id;

        that.options = options;

        that.ul = $('<ul tabindex="-1" role="listbox" aria-hidden="true" unselectable="on" class="k-list k-reset"/>')
                    .css({ overflow: support.kineticScrollNeeded ? "" : "auto" })
                    .on(CLICK, LI, proxy(that._click, that))
                    .on("mouseenter" + ns, LI, function () { $(this).addClass(HOVER); })
                    .on("mouseleave" + ns, LI, function () { $(this).removeClass(HOVER); });

        that.list = $("<div class='k-list-container'/>")
                    .append(that.ul)
                    .on(MOUSEDOWN, preventDefault);

        if (id) {
            that._timeViewID = id + "_timeview";
            that._optionID = id + "_option_selected";

            that.ul.attr(ID, that._timeViewID);
        }

        that._popup();
        that._heightHandler = proxy(that._height, that);

        that.template = kendo.template('<li tabindex="-1" role="option" class="k-item" unselectable="on">#=data#</li>', { useWithBlock: false });
    };

    TimeView.prototype = {
        current: function (candidate) {
            var that = this,
                active = that.options.active;

            if (candidate !== undefined) {
                if (that._current) {
                    that._current
                        .removeClass(SELECTED)
                        .removeAttr(ARIA_SELECTED)
                        .removeAttr(ID);
                }

                if (candidate) {
                    candidate = $(candidate).addClass(SELECTED)
                                            .attr(ID, that._optionID)
                                            .attr(ARIA_SELECTED, true);

                    that.scroll(candidate[0]);
                }

                that._current = candidate;

                if (active) {
                    active(candidate);
                }
            } else {
                return that._current;
            }
        },

        close: function () {
            this.popup.close();
        },

        destroy: function () {
            var that = this;

            that.ul.off(ns);
            that.list.off(ns);

            if (that._touchScroller) {
                that._touchScroller.destroy();
            }

            that.popup.destroy();
        },

        open: function () {
            var that = this;

            if (!that.ul[0].firstChild) {
                that.bind();
            }

            that.popup.open();
            if (that._current) {
                that.scroll(that._current[0]);
            }
        },

        dataBind: function (dates) {
            var that = this,
                options = that.options,
                format = options.format,
                toString = kendo.toString,
                template = that.template,
                length = dates.length,
                idx = 0,
                date,
                html = "";

            for (; idx < length; idx++) {
                date = dates[idx];

                if (isInRange(date, options.min, options.max)) {
                    html += template(toString(date, format, options.culture));
                }
            }

            that._html(html);
        },

        refresh: function () {
            var that = this,
                options = that.options,
                format = options.format,
                offset = dst(),
                ignoreDST = offset < 0,
                min = options.min,
                max = options.max,
                msMin = getMilliseconds(min),
                msMax = getMilliseconds(max),
                msInterval = options.interval * MS_PER_MINUTE,
                toString = kendo.toString,
                template = that.template,
                start = new DATE(+min),
                startDay = start.getDate(),
                msStart, lastIdx,
                idx = 0, length,
                html = "";

            if (ignoreDST) {
                length = (MS_PER_DAY + (offset * MS_PER_MINUTE)) / msInterval;
            } else {
                length = MS_PER_DAY / msInterval;
            }


            if (msMin != msMax) {
                if (msMin > msMax) {
                    msMax += MS_PER_DAY;
                }

                length = ((msMax - msMin) / msInterval) + 1;
            }

            lastIdx = parseInt(length, 10);

            for (; idx < length; idx++) {
                if (idx) {
                    setTime(start, msInterval, ignoreDST);
                }

                if (msMax && lastIdx == idx) {
                    msStart = getMilliseconds(start);
                    if (startDay < start.getDate()) {
                        msStart += MS_PER_DAY;
                    }

                    if (msStart > msMax) {
                        start = new DATE(+max);
                    }
                }

                html += template(toString(start, format, options.culture));
            }

            that._html(html);
        },

        bind: function () {
            var that = this,
                dates = that.options.dates;

            if (dates && dates[0]) {
                that.dataBind(dates);
            } else {
                that.refresh();
            }
        },

        _html: function (html) {
            var that = this;

            that.ul[0].innerHTML = html;

            that.popup.unbind(OPEN, that._heightHandler);
            that.popup.one(OPEN, that._heightHandler);

            that.current(null);
            that.select(that._value);
        },

        scroll: function (item) {
            if (!item) {
                return;
            }

            var ul = this.ul[0],
                itemOffsetTop = item.offsetTop,
                itemOffsetHeight = item.offsetHeight,
                ulScrollTop = ul.scrollTop,
                ulOffsetHeight = ul.clientHeight,
                bottomDistance = itemOffsetTop + itemOffsetHeight,
                touchScroller = this._touchScroller,
                elementHeight;

            if (touchScroller) {
                elementHeight = this.list.height();

                if (itemOffsetTop > elementHeight) {
                    itemOffsetTop = itemOffsetTop - elementHeight + itemOffsetHeight;
                }

                touchScroller.scrollTo(0, -itemOffsetTop);
            } else {
                ul.scrollTop = ulScrollTop > itemOffsetTop ?
                               itemOffsetTop : bottomDistance > (ulScrollTop + ulOffsetHeight) ?
                               bottomDistance - ulOffsetHeight : ulScrollTop;
            }
        },

        select: function (li) {
            var that = this,
                options = that.options,
                current = that._current;

            if (li instanceof Date) {
                li = kendo.toString(li, options.format, options.culture);
            }

            if (typeof li === "string") {
                if (!current || current.text() !== li) {
                    li = $.grep(that.ul[0].childNodes, function (node) {
                        return (node.textContent || node.innerText) == li;
                    });

                    li = li[0] ? li : null;
                } else {
                    li = current;
                }
            }

            that.current(li);
        },

        setOptions: function (options) {
            var old = this.options;

            options.min = parse(options.min);
            options.max = parse(options.max);

            this.options = extend(old, options, {
                active: old.active,
                change: old.change,
                close: old.close,
                open: old.open
            });

            this.bind();
        },

        toggle: function () {
            var that = this;

            if (that.popup.visible()) {
                that.close();
            } else {
                that.open();
            }
        },

        value: function (value) {
            var that = this;

            that._value = value;
            if (that.ul[0].firstChild) {
                that.select(value);
            }
        },

        _click: function (e) {
            var that = this,
                li = $(e.currentTarget);

            if (!e.isDefaultPrevented()) {
                that.select(li);
                that.options.change(li.text(), true);
                that.close();
            }
        },

        _height: function () {
            var that = this;
            var list = that.list;
            var parent = list.parent(".k-animation-container");
            var height = that.options.height;

            if (that.ul[0].children.length) {
                list.add(parent)
                    .show()
                    .height(that.ul[0].scrollHeight > height ? height : "auto")
                    .hide();
            }
        },

        _parse: function (value) {
            var that = this,
                options = that.options,
                current = that._value || TODAY;

            if (value instanceof DATE) {
                return value;
            }

            value = parse(value, options.parseFormats, options.culture);

            if (value) {
                value = new DATE(current.getFullYear(),
                                 current.getMonth(),
                                 current.getDate(),
                                 value.getHours(),
                                 value.getMinutes(),
                                 value.getSeconds(),
                                 value.getMilliseconds());
            }

            return value;
        },

        _adjustListWidth: function () {
            var list = this.list,
                width = list[0].style.width,
                wrapper = this.options.anchor,
                computedStyle, computedWidth;

            if (!list.data("width") && width) {
                return;
            }

            computedStyle = window.getComputedStyle ? window.getComputedStyle(wrapper[0], null) : 0;
            computedWidth = computedStyle ? parseFloat(computedStyle.width) : wrapper.outerWidth();

            if (computedStyle && (browser.mozilla || browser.msie)) { // getComputedStyle returns different box in FF and IE.
                computedWidth += parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight) + parseFloat(computedStyle.borderLeftWidth) + parseFloat(computedStyle.borderRightWidth);
            }

            width = computedWidth - (list.outerWidth() - list.width());

            list.css({
                fontFamily: wrapper.css("font-family"),
                width: width
            })
            .data("width", width);
        },

        _popup: function () {
            var that = this,
                list = that.list,
                options = that.options,
                anchor = options.anchor;

            that.popup = new ui.Popup(list, extend(options.popup, {
                anchor: anchor,
                open: options.open,
                close: options.close,
                animation: options.animation,
                isRtl: support.isRtl(options.anchor)
            }));

            that._touchScroller = kendo.touchScroller(that.popup.element);
        },

        move: function (e) {
            var that = this,
                key = e.keyCode,
                ul = that.ul[0],
                current = that._current,
                down = key === keys.DOWN;

            if (key === keys.UP || down) {
                if (e.altKey) {
                    that.toggle(down);
                    return;
                } else if (down) {
                    current = current ? current[0].nextSibling : ul.firstChild;
                } else {
                    current = current ? current[0].previousSibling : ul.lastChild;
                }

                if (current) {
                    that.select(current);
                }

                that.options.change(that._current.text());
                e.preventDefault();

            } else if (key === keys.ENTER || key === keys.TAB || key === keys.ESC) {
                e.preventDefault();
                if (current) {
                    that.options.change(current.text(), true);
                }
                that.close();
            }
        }
    };

    function setTime(date, time, ignoreDST) {
        var offset = date.getTimezoneOffset(),
            offsetDiff;

        date.setTime(date.getTime() + time);

        if (!ignoreDST) {
            offsetDiff = date.getTimezoneOffset() - offset;
            date.setTime(date.getTime() + offsetDiff * MS_PER_MINUTE);
        }
    }

    function dst() {
        var today = new DATE(),
            midnight = new DATE(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0),
            noon = new DATE(today.getFullYear(), today.getMonth(), today.getDate(), 12, 0, 0);

        return -1 * (midnight.getTimezoneOffset() - noon.getTimezoneOffset());
    }

    function getMilliseconds(date) {
        return date.getHours() * 60 * MS_PER_MINUTE + date.getMinutes() * MS_PER_MINUTE + date.getSeconds() * 1000 + date.getMilliseconds();
    }

    function isInRange(value, min, max) {
        var msMin = getMilliseconds(min),
            msMax = getMilliseconds(max),
            msValue;

        if (!value || msMin == msMax) {
            return true;
        }

        msValue = getMilliseconds(value);

        if (msMin > msValue) {
            msValue += MS_PER_DAY;
        }

        if (msMax < msMin) {
            msMax += MS_PER_DAY;
        }

        return msValue >= msMin && msValue <= msMax;
    }

    TimeView.getMilliseconds = getMilliseconds;

    kendo.TimeView = TimeView;

    var TimePicker = Widget.extend({
        init: function (element, options) {
            var that = this, ul, timeView, disabled;

            Widget.fn.init.call(that, element, options);

            element = that.element;
            options = that.options;

            options.min = parse(element.attr("min")) || parse(options.min);
            options.max = parse(element.attr("max")) || parse(options.max);

            normalize(options);

            that._wrapper();

            that.timeView = timeView = new TimeView(extend({}, options, {
                id: element.attr(ID),
                anchor: that.wrapper,
                format: options.format,
                change: function (value, trigger) {
                    if (trigger) {
                        that._change(value);
                    } else {
                        element.val(value);
                    }
                },
                open: function (e) {
                    that.timeView._adjustListWidth();

                    if (that.trigger(OPEN)) {
                        e.preventDefault();
                    } else {
                        element.attr(ARIA_EXPANDED, true);
                        ul.attr(ARIA_HIDDEN, false);
                    }
                },
                close: function (e) {
                    if (that.trigger(CLOSE)) {
                        e.preventDefault();
                    } else {
                        element.attr(ARIA_EXPANDED, false);
                        ul.attr(ARIA_HIDDEN, true);
                    }
                },
                active: function (current) {
                    element.removeAttr(ARIA_ACTIVEDESCENDANT);
                    if (current) {
                        element.attr(ARIA_ACTIVEDESCENDANT, timeView._optionID);
                    }
                }
            }));
            ul = timeView.ul;

            that._icon();
            that._reset();

            try {
                element[0].setAttribute("type", "text");
            } catch (e) {
                element[0].type = "text";
            }

            element.addClass("k-input")
                   .attr({
                       "role": "combobox",
                       "aria-expanded": false,
                       "aria-owns": timeView._timeViewID
                   });

            disabled = element.is("[disabled]");
            if (disabled) {
                that.enable(false);
            } else {
                that.readonly(element.is("[readonly]"));
            }

            that._old = that._update(options.value || that.element.val());
            that._oldText = element.val();

            kendo.notify(that);
        },

        options: {
            name: "TimePicker",
            min: TODAY,
            max: TODAY,
            format: "",
            dates: [],
            parseFormats: [],
            value: null,
            interval: 30,
            height: 200,
            animation: {}
        },

        events: [
         OPEN,
         CLOSE,
         CHANGE
        ],

        setOptions: function (options) {
            var that = this;
            var value = that._value;

            Widget.fn.setOptions.call(that, options);
            options = that.options;

            normalize(options);

            that.timeView.setOptions(options);

            if (value) {
                that.element.val(kendo.toString(value, options.format, options.culture));
            }
        },

        dataBind: function (dates) {
            if (isArray(dates)) {
                this.timeView.dataBind(dates);
            }
        },

        _editable: function (options) {
            var that = this,
                disable = options.disable,
                readonly = options.readonly,
                arrow = that._arrow.off(ns),
                element = that.element.off(ns),
                wrapper = that._inputWrapper.off(ns);

            if (!readonly && !disable) {
                wrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED)
                    .on(HOVEREVENTS, that._toggleHover);

                element.removeAttr(DISABLED)
                       .removeAttr(READONLY)
                       .attr(ARIA_DISABLED, false)
                       .attr(ARIA_READONLY, false)
                       .on("keydown" + ns, proxy(that._keydown, that))
                       .on("focusout" + ns, proxy(that._blur, that))
                       .on("focus" + ns, function () {
                           that._inputWrapper.addClass(FOCUSED);
                       });

                arrow.on(CLICK, proxy(that._click, that))
                   .on(MOUSEDOWN, preventDefault);
            } else {
                wrapper
                    .addClass(disable ? STATEDISABLED : DEFAULT)
                    .removeClass(disable ? DEFAULT : STATEDISABLED);

                element.attr(DISABLED, disable)
                       .attr(READONLY, readonly)
                       .attr(ARIA_DISABLED, disable)
                       .attr(ARIA_READONLY, readonly);
            }
        },

        readonly: function (readonly) {
            this._editable({
                readonly: readonly === undefined ? true : readonly,
                disable: false
            });
        },

        enable: function (enable) {
            this._editable({
                readonly: false,
                disable: !(enable = enable === undefined ? true : enable)
            });
        },

        destroy: function () {
            var that = this;

            Widget.fn.destroy.call(that);

            that.timeView.destroy();

            that.element.off(ns);
            that._arrow.off(ns);
            that._inputWrapper.off(ns);

            if (that._form) {
                that._form.off("reset", that._resetHandler);
            }
        },

        close: function () {
            this.timeView.close();
        },

        open: function () {
            this.timeView.open();
        },

        min: function (value) {
            return this._option("min", value);
        },

        max: function (value) {
            return this._option("max", value);
        },

        value: function (value) {
            var that = this;

            if (value === undefined) {
                return that._value;
            }

            that._old = that._update(value);

            if (that._old === null) {
                that.element.val("");
            }

            that._oldText = that.element.val();
        },

        _blur: function () {
            var that = this,
                value = that.element.val();

            that.close();
            if (value !== that._oldText) {
                that._change(value);
            }
            that._inputWrapper.removeClass(FOCUSED);
        },

        _click: function () {
            var that = this,
                element = that.element;

            that.timeView.toggle();

            if (!support.touch && element[0] !== activeElement()) {
                element.focus();
            }
        },

        _change: function (value) {
            var that = this;

            value = that._update(value);

            if (+that._old != +value) {
                that._old = value;
                that._oldText = that.element.val();

                // trigger the DOM change event so any subscriber gets notified
                that.element.trigger(CHANGE);

                that.trigger(CHANGE);
            }
        },

        _icon: function () {
            var that = this,
                element = that.element,
                arrow;

            arrow = element.next("span.k-select");

            if (!arrow[0]) {
                arrow = $('<span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-clock">select</span></span>').insertAfter(element);
            }

            that._arrow = arrow.attr({
                "role": "button",
                "aria-controls": that.timeView._timeViewID
            });
        },

        _keydown: function (e) {
            var that = this,
                key = e.keyCode,
                timeView = that.timeView,
                value = that.element.val();

            if (timeView.popup.visible() || e.altKey) {
                timeView.move(e);
            } else if (key === keys.ENTER && value !== that._oldText) {
                that._change(value);
            }
        },

        _option: function (option, value) {
            var that = this,
                options = that.options;

            if (value === undefined) {
                return options[option];
            }

            value = that.timeView._parse(value);

            if (!value) {
                return;
            }

            value = new DATE(+value);

            options[option] = value;
            that.timeView.options[option] = value;
            that.timeView.bind();
        },

        _toggleHover: function (e) {
            $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
        },

        _update: function (value) {
            var that = this,
                options = that.options,
                timeView = that.timeView,
                date = timeView._parse(value);

            if (!isInRange(date, options.min, options.max)) {
                date = null;
            }

            that._value = date;
            that.element.val(date ? kendo.toString(date, options.format, options.culture) : value);
            timeView.value(date);

            return date;
        },

        _wrapper: function () {
            var that = this,
                element = that.element,
                wrapper;

            wrapper = element.parents(".k-timepicker");

            if (!wrapper[0]) {
                wrapper = element.wrap(SPAN).parent().addClass("k-picker-wrap k-state-default");
                wrapper = wrapper.wrap(SPAN).parent();
            }

            wrapper[0].style.cssText = element[0].style.cssText;
            that.wrapper = wrapper.addClass("k-widget k-timepicker k-header")
                                  .addClass(element[0].className);

            element.css({
                width: "100%",
                height: element[0].style.height
            });

            that._inputWrapper = $(wrapper[0].firstChild);
        },

        _reset: function () {
            var that = this,
                element = that.element,
                formId = element.attr("form"),
                form = formId ? $("#" + formId) : element.closest("form");

            if (form[0]) {
                that._resetHandler = function () {
                    that.value(element[0].defaultValue);
                };

                that._form = form.on("reset", that._resetHandler);
            }
        }
    });

    function normalize(options) {
        var parseFormats = options.parseFormats;

        options.format = extractFormat(options.format || kendo.getCulture(options.culture).calendars.standard.patterns.t);

        parseFormats = isArray(parseFormats) ? parseFormats : [parseFormats];
        parseFormats.splice(0, 0, options.format);
        options.parseFormats = parseFormats;
    }

    function preventDefault(e) {
        e.preventDefault();
    }

    ui.plugin(TimePicker);

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        Class = kendo.Class,
        Widget = kendo.ui.Widget,
        proxy = $.proxy,
        isFunction = kendo.isFunction,

        TOOLBAR = "k-toolbar",
        BUTTON = "k-button",
        OVERFLOW_BUTTON = "k-overflow-button",
        TOGGLE_BUTTON = "k-toggle-button",
        BUTTON_GROUP = "k-button-group",
        SPLIT_BUTTON = "k-split-button",
        SEPARATOR = "k-separator",
        POPUP = "k-popup",

        RESIZABLE_TOOLBAR = "k-toolbar-resizable",
        STATE_ACTIVE = "k-state-active",
        STATE_DISABLED = "k-state-disabled",
        GROUP_START = "k-group-start",
        GROUP_END = "k-group-end",
        PRIMARY = "k-primary",

        ICON = "k-icon",
        ICON_PREFIX = "k-i-",
        BUTTON_ICON = "k-button-icon",
        BUTTON_ICON_TEXT = "k-button-icontext",

        LIST_CONTAINER = "k-list-container k-split-container",
        SPLIT_BUTTON_ARROW = "k-split-button-arrow",

        OVERFLOW_ANCHOR = "k-overflow-anchor",
        OVERFLOW_CONTAINER = "k-overflow-container",
        FIRST_TOOLBAR_VISIBLE = "k-toolbar-first-visible",
        LAST_TOOLBAR_VISIBLE = "k-toolbar-last-visible",

        CLICK = "click",
        TOGGLE = "toggle",
        OPEN = "open",
        CLOSE = "close",
        OVERFLOW_OPEN = "overflowOpen",
        OVERFLOW_CLOSE = "overflowClose",

        OVERFLOW_NEVER = "never",
        OVERFLOW_AUTO = "auto",
        OVERFLOW_ALWAYS = "always",
        OVERFLOW_HIDDEN = "k-overflow-hidden",

        KENDO_UID_ATTR = kendo.attr("uid"),

        template = kendo.template,
        components = {
            button: {
                base: createButton,
                toolbar: createToolbarButton,
                overflow: createOverflowButton
            },

            buttonGroup: {
                base: function (options, initializer, element) {
                    var items = options.buttons,
                        item;

                    if (!items) {
                        return;
                    }

                    if (options.attributes) {
                        element.attr(options.attributes);
                    }

                    element.data({ type: "buttonGroup" });
                    element.attr(KENDO_UID_ATTR, options.uid);

                    for (var i = 0; i < items.length; i++) {
                        if (!items[i].uid) {
                            items[i].uid = kendo.guid();
                        }
                        item = initializer($.extend({ mobile: options.mobile }, items[i]));
                        item.appendTo(element);
                    }

                    element.children().first().addClass(GROUP_START);
                    element.children().last().addClass(GROUP_END);
                },
                toolbar: function (options) {
                    var element = $('<div></div>');

                    components.buttonGroup.base(options, components.button.toolbar, element);

                    element.addClass(BUTTON_GROUP);

                    if (options.align) {
                        element.addClass("k-align-" + options.align);
                    }

                    if (options.id) {
                        element.attr("id", options.id);
                    }

                    return element;
                },
                overflow: function (options) {
                    var element = $('<li></li>');

                    components.buttonGroup.base(options, components.button.overflow, element);

                    element.addClass((options.mobile ? "" : BUTTON_GROUP) + " k-overflow-group");

                    if (options.id) {
                        element.attr("id", options.id + "_overflow");
                    }

                    return element;
                }
            },

            splitButton: {
                toolbar: function (options) {
                    var element = $('<div class="' + SPLIT_BUTTON + '"></div>'),
                        mainButton = components.button.toolbar(options),
                        arrowButton = $('<a class="' + BUTTON + " " + SPLIT_BUTTON_ARROW + '"><span class="' +
                                       (options.mobile ? "km-icon km-arrowdown" : "k-icon k-i-arrow-s") + '"></span></a>'),
                        popupElement = $('<ul class="' + LIST_CONTAINER + '"></ul>'),
                        popup,
                        items = options.menuButtons,
                        item;

                    mainButton.appendTo(element);
                    arrowButton.appendTo(element);
                    popupElement.appendTo(element);

                    for (var i = 0; i < items.length; i++) {
                        item = components.button.toolbar($.extend({ mobile: options.mobile, click: options.click }, items[i]));
                        item.wrap("<li></li>").parent().appendTo(popupElement);
                    }

                    if (options.align) {
                        element.addClass("k-align-" + options.align);
                    }

                    if (!options.id) {
                        options.id = options.uid;
                    }

                    element.attr("id", options.id + "_wrapper");
                    popupElement.attr("id", options.id + "_optionlist")
                                .attr(KENDO_UID_ATTR, options.rootUid);

                    if (options.mobile) {
                        popupElement = actionSheetWrap(popupElement);
                    }

                    popup = popupElement.kendoPopup({
                        appendTo: options.mobile ? $(options.mobile).children(".km-pane") : null,
                        anchor: element,
                        copyAnchorStyles: false,
                        animation: options.animation,
                        open: adjustPopupWidth
                    }).data("kendoPopup");

                    element.data({
                        type: "splitButton",
                        kendoPopup: popup
                    });
                    element.attr(KENDO_UID_ATTR, options.uid);

                    return element;
                },
                overflow: function (options) {
                    var element = $('<li class="' + SPLIT_BUTTON + '"></li>'),
                        mainButton = components.button.overflow(options),
                        items = options.menuButtons,
                        item;

                    mainButton.appendTo(element);

                    for (var i = 0; i < items.length; i++) {
                        item = components.button.overflow($.extend({ mobile: options.mobile }, items[i]));
                        item.appendTo(element);
                    }

                    if (options.id) {
                        element.attr("id", options.id + "_overflow");
                    }

                    element.data({ type: "splitButton" });
                    element.attr(KENDO_UID_ATTR, options.uid);

                    return element;
                }
            },

            separator: {
                base: function (options, overflow) {
                    var element = overflow ? $('<li>&nbsp;</li>') : $('<div>&nbsp;</div>');
                    element.data({ type: "separator" });
                    element.attr(KENDO_UID_ATTR, options.uid);

                    if (options.attributes) {
                        element.attr(options.attributes);
                    }

                    element.addClass(SEPARATOR);

                    return element;
                },
                toolbar: function (options) {
                    return components.separator.base(options, false);
                },
                overflow: function (options) {
                    return components.separator.base(options, true);
                }
            },

            overflowAnchor: '<div class="k-overflow-anchor"></div>',

            overflowContainer: '<ul class="k-overflow-container k-list-container"></ul>'
        };

    function createButton(options) {
        var element = options.useButtonTag ? $('<button></button>') : $('<a></a>');

        element.data({ type: "button" });
        element.attr(KENDO_UID_ATTR, options.uid);

        if (options.attributes) {
            element.attr(options.attributes);
        }

        if (options.togglable) {
            element.addClass(TOGGLE_BUTTON);
            if (options.selected) {
                element.addClass(STATE_ACTIVE);
            }
        }

        if (options.enable === false) {
            element.addClass(STATE_DISABLED);
        }

        if (options.url !== undefined && !options.useButtonTag) {
            element.attr("href", options.url);
            if (options.mobile) {
                element.attr(kendo.attr("role"), "button");
            }
        }

        if (options.group) {
            element.attr(kendo.attr("group"), options.group);
        }

        if (!options.togglable && options.click && isFunction(options.click)) {
            element.data("click", options.click);
        }

        if (options.togglable && options.toggle && isFunction(options.toggle)) {
            element.data("toggle", options.toggle);
        }

        return element;
    }

    function createToolbarButton(options) {
        var element = components.button.base(options),
                hasIcon;

        element.addClass(BUTTON);

        if (options.primary) {
            element.addClass(PRIMARY);
        }

        if (options.align) {
            element.addClass("k-align-" + options.align);
        }

        if (options.id) {
            element.attr("id", options.id);
        }

        if (options.showText != "overflow" && options.text) {
            if (options.mobile) {
                element.html('<span class="km-text">' + options.text + "</span>");
            } else {
                element.html(options.text);
            }
        }

        hasIcon = (options.showIcon != "overflow") && (options.icon || options.spriteCssClass || options.imageUrl);

        if (hasIcon) {
            addGraphic(options, element);
        }

        return element;
    }

    function createOverflowButton(options) {
        var element = components.button.base(options),
                hasIcon;

        element.addClass(OVERFLOW_BUTTON + " " + BUTTON);

        if (options.primary) {
            element.addClass(PRIMARY);
        }

        if (options.id) {
            element.attr("id", options.id + "_overflow");
        }

        if (options.showText != "toolbar" && options.text) {
            if (options.mobile) {
                element.html('<span class="km-text">' + options.text + "</span>");
            } else {
                element.html(options.text);
            }
        }

        hasIcon = (options.showIcon != "toolbar") && (options.icon || options.spriteCssClass || options.imageUrl);

        if (hasIcon) {
            addGraphic(options, element);
        }

        return element;
    }

    function addGraphic(options, element) {
        var icon = options.icon,
                spriteCssClass = options.spriteCssClass,
                imageUrl = options.imageUrl,
                isEmpty, span, img;

        if (spriteCssClass || imageUrl || icon) {
            isEmpty = true;

            element.contents().not("span.k-sprite,span." + ICON + ",img.k-image").each(function (idx, el) {
                if (el.nodeType == 1 || el.nodeType == 3 && $.trim(el.nodeValue).length > 0) {
                    isEmpty = false;
                }
            });

            if (isEmpty) {
                element.addClass(BUTTON_ICON);
            } else {
                element.addClass(BUTTON_ICON_TEXT);
            }
        }

        if (icon) {
            span = element.children("span." + ICON).first();
            if (!span[0]) {
                span = $('<span class="' + ICON + '"></span>').prependTo(element);
            }
            span.addClass(ICON_PREFIX + icon);
        } else if (spriteCssClass) {
            span = element.children("span.k-sprite").first();
            if (!span[0]) {
                span = $('<span class="k-sprite"></span>').prependTo(element);
            }
            span.addClass(spriteCssClass);
        } else if (imageUrl) {
            img = element.children("img.k-image").first();
            if (!img[0]) {
                img = $('<img alt="icon" class="k-image" />').prependTo(element);
            }
            img.attr("src", imageUrl);
        }
    }

    function adjustPopupWidth(e) {
        var anchor = this.options.anchor,
                computedWidth = anchor.outerWidth(),
                width;

        kendo.wrap(this.element).addClass("k-split-wrapper");

        if (this.element.css("box-sizing") !== "border-box") {
            width = computedWidth - (this.element.outerWidth() - this.element.width());
        } else {
            width = computedWidth;
        }

        this.element.css({
            fontFamily: anchor.css("font-family"),
            "min-width": width
        });
    }

    function toggleActive(e) {
        if (!e.target.is(".k-toggle-button")) {
            e.target.toggleClass(STATE_ACTIVE, e.type == "press");
        }
    }

    function actionSheetWrap(element) {
        element = $(element);

        return element.hasClass("km-actionsheet") ? element.closest(".km-popup-wrapper") : element.addClass("km-widget km-actionsheet")
                             .wrap('<div class="km-actionsheet-wrapper km-actionsheet-tablet km-widget km-popup"></div>').parent()
                             .wrap('<div class="km-popup-wrapper k-popup"></div>').parent();
    }

    var ToolBar = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            options = that.options;
            element = that.wrapper = that.element;

            element.addClass(TOOLBAR + " k-widget");

            this.uid = kendo.guid();
            element.attr(KENDO_UID_ATTR, this.uid);

            that.isMobile = (typeof options.mobile === "boolean") ? options.mobile : that.element.closest(".km-root")[0];
            that.animation = that.isMobile ? { open: { effects: "fade"}} : {};

            if (that.isMobile) {
                element.addClass("km-widget");
                ICON = "km-icon";
                ICON_PREFIX = "km-";
                BUTTON = "km-button";
                BUTTON_GROUP = "km-buttongroup km-widget";
                STATE_ACTIVE = "km-state-active";
                STATE_DISABLED = "km-state-disabled";
            }

            if (options.resizable) {
                that._renderOverflow();
                element.addClass(RESIZABLE_TOOLBAR);

                that.overflowUserEvents = new kendo.UserEvents(that.element, {
                    threshold: 5,
                    allowSelection: true,
                    filter: "." + OVERFLOW_ANCHOR,
                    tap: proxy(that._toggleOverflow, that)
                });

                kendo.onResize(function () {
                    that.resize();
                });
            } else {
                that.popup = { element: $([]) };
            }

            if (options.items && options.items.length) {
                for (var i = 0; i < options.items.length; i++) {
                    that.add(options.items[i]);
                }
            }

            that.userEvents = new kendo.UserEvents(document, {
                threshold: 5,
                allowSelection: true,
                filter:
                        "[" + KENDO_UID_ATTR + "=" + this.uid + "] ." + BUTTON + ", " +
                        "[" + KENDO_UID_ATTR + "=" + this.uid + "] ." + OVERFLOW_BUTTON,
                tap: proxy(that._buttonClick, that),
                press: toggleActive,
                release: toggleActive
            });

            if (options.resizable) {
                this._toggleOverflowAnchor();
            }

            kendo.notify(that);
        },

        events: [
                CLICK,
                TOGGLE,
                OPEN,
                CLOSE,
                OVERFLOW_OPEN,
                OVERFLOW_CLOSE
            ],

        options: {
            name: "ToolBar",
            items: [],
            resizable: true,
            mobile: null
        },

        destroy: function () {
            var that = this;

            that.element.find("." + SPLIT_BUTTON).each(function (idx, element) {
                $(element).data("kendoPopup").destroy();
            });

            that.userEvents.destroy();

            if (that.options.resizable) {
                that.overflowUserEvents.destroy();
                that.popup.destroy();
            }

            Widget.fn.destroy.call(that);
        },

        add: function (options) {
            var component = components[options.type],
                    template = options.template,
                    element, that = this,
                    itemClasses = that.isMobile ? "" : "k-item k-state-default",
                    overflowTemplate = options.overflowTemplate,
                    overflowElement;

            $.extend(options, {
                uid: kendo.guid(),
                animation: that.animation,
                mobile: that.isMobile,
                rootUid: that.uid
            });

            if (template && !overflowTemplate) {
                options.overflow = OVERFLOW_NEVER;
            }

            //add the command in the overflow popup
            if (options.overflow !== OVERFLOW_NEVER && that.options.resizable) {
                if (overflowTemplate) { //template command
                    overflowElement = isFunction(overflowTemplate) ? $(overflowTemplate(options)) : $(overflowTemplate);
                } else if (component) { //build-in command
                    overflowElement = (component.overflow || $.noop)(options);
                }

                if (overflowElement && overflowElement.length) {
                    if (overflowElement.prop("tagName") !== "LI") {
                        overflowElement.removeAttr(KENDO_UID_ATTR);
                        overflowElement = overflowElement.wrap("<li></li>").parent();
                        overflowElement.attr(KENDO_UID_ATTR, options.uid);
                    }
                    that._attributes(overflowElement, options);
                    overflowElement.addClass(itemClasses).appendTo(that.popup.container);

                    if (overflowElement.data("overflow") === OVERFLOW_AUTO) {
                        overflowElement.addClass(OVERFLOW_HIDDEN);
                    }

                    that.angular("compile", function () {
                        return { elements: overflowElement.get() };
                    });
                }
            }

            //add the command in the toolbar container
            if (options.overflow !== OVERFLOW_ALWAYS) {
                if (template) { //template command
                    element = isFunction(template) ? template(options) : template;

                    if (!(element instanceof jQuery)) {
                        element = $(element.replace(/^\s+|\s+$/g, ''));
                    }

                    element = element.wrap("<div></div>").parent();
                    element.attr(KENDO_UID_ATTR, options.uid);
                } else if (component) { //build-in command
                    element = (component.toolbar || $.noop)(options);
                }

                if (element && element.length) {
                    that._attributes(element, options);

                    if (that.options.resizable) {
                        element.appendTo(that.element).css("visibility", "hidden");
                        that._shrink(that.element.innerWidth());
                        element.css("visibility", "visible");
                    } else {
                        element.appendTo(that.element);
                    }

                    that.angular("compile", function () {
                        return { elements: element.get() };
                    });
                }
            }
        },

        remove: function (element) {
            var commandElement = this.element.find(element),
                    type, uid;

            if (!commandElement.length && this.options.resizable) {
                commandElement = this.popup.element.find(element);
                uid = commandElement.parent().attr(KENDO_UID_ATTR);
            } else {
                uid = commandElement.attr(KENDO_UID_ATTR);
            }

            type = commandElement.data("type");

            if (commandElement.parent("." + SPLIT_BUTTON).data("type")) {
                type = "splitButton";
                commandElement = commandElement.parent();
            }

            if (type === "splitButton") {
                commandElement.data("kendoPopup").destroy();
            }

            commandElement
                    .add(this.popup.element.find("[" + KENDO_UID_ATTR + "='" + uid + "']"))
                    .remove();
        },

        enable: function (element, enable) {
            var uid = this.element.find(element).attr(KENDO_UID_ATTR);

            if (!uid && this.popup) {
                uid = this.popup.element.find(element).parent("li").attr(KENDO_UID_ATTR);
            }

            if (typeof enable == "undefined") {
                enable = true;
            }

            if (enable) {
                $("[" + KENDO_UID_ATTR + "='" + uid + "']").removeClass(STATE_DISABLED);
            } else {
                $("[" + KENDO_UID_ATTR + "='" + uid + "']").addClass(STATE_DISABLED);
            }
        },

        getSelectedFromGroup: function (groupName) {
            return this.element.find("." + TOGGLE_BUTTON + "[data-group='" + groupName + "']").filter("." + STATE_ACTIVE);
        },

        toggle: function (button, checked) {
            var element = $(button),
                    uid = element.data("uid"),
                    group = element.data("group"),
                    twinElement;

            if (element.hasClass(TOGGLE_BUTTON)) {

                if (group) { //find all buttons from the same group
                    this.element
                            .add(this.popup.element)
                            .find("." + TOGGLE_BUTTON + "[data-group='" + group + "']")
                            .filter("." + STATE_ACTIVE)
                            .removeClass(STATE_ACTIVE);
                }

                if ($.contains(this.element[0], element[0])) {
                    twinElement = this.popup.element.find("[" + KENDO_UID_ATTR + "='" + uid + "']");
                    if (twinElement.prop("tagName") === "LI") {
                        twinElement = twinElement.find("." + TOGGLE_BUTTON + ":first");
                    }
                } else {
                    uid = uid ? uid : element.parent().data("uid");
                    twinElement = this.element.find("[" + KENDO_UID_ATTR + "='" + uid + "']");
                }

                element.add(twinElement).toggleClass(STATE_ACTIVE, checked);
            }
        },

        _attributes: function (element, options) {
            element.attr(kendo.attr("overflow"), options.overflow || OVERFLOW_AUTO);
        },

        _renderOverflow: function () {
            var that = this,
                    overflowContainer = components.overflowContainer;

            that.overflowAnchor = $(components.overflowAnchor).addClass(BUTTON);

            that.element.append(that.overflowAnchor);

            if (that.isMobile) {
                that.overflowAnchor.append('<span class="km-icon km-more"></span>');
                overflowContainer = actionSheetWrap(overflowContainer);
            } else {
                that.overflowAnchor.append('<span class="k-icon k-i-more"></span>');
            }

            that.popup = new kendo.ui.Popup(overflowContainer, {
                origin: "bottom right",
                position: "top right",
                anchor: that.overflowAnchor,
                animation: that.animation,
                appendTo: that.isMobile ? $(that.isMobile).children(".km-pane") : null,
                copyAnchorStyles: false,
                open: function (e) {
                    var wrapper = kendo.wrap(that.popup.element)
                            .addClass("k-overflow-wrapper");

                    if (!that.isMobile) {
                        wrapper.css("margin-left", (wrapper.outerWidth() - wrapper.width()) / 2 + 1);
                    } else {
                        that.popup.container.css("max-height", (parseFloat($(".km-content:visible").innerHeight()) - 15) + "px");
                    }

                    if (that.trigger(OVERFLOW_OPEN)) {
                        e.preventDefault();
                    }
                },
                close: function (e) {
                    if (that.trigger(OVERFLOW_CLOSE)) {
                        e.preventDefault();
                    }
                }
            });

            if (that.isMobile) {
                that.popup.container = that.popup.element.find("." + OVERFLOW_CONTAINER);
            } else {
                that.popup.container = that.popup.element;
            }

            that.popup.container.attr(KENDO_UID_ATTR, this.uid);
        },

        _toggleOverflowAnchor: function () {
            if (this.popup.element.children(":not(." + OVERFLOW_HIDDEN + ", ." + POPUP + ")").length > 0) {
                this.overflowAnchor.css({
                    visibility: "visible",
                    width: ""
                });
            } else {
                this.overflowAnchor.css({
                    visibility: "hidden",
                    width: "1px"
                });
            }
        },

        _buttonClick: function (e) {
            var that = this, popup,
                    target, splitContainer,
                    isDisabled, isChecked,
                    group, handler, eventData, id;

            e.preventDefault();

            target = $(e.target).closest("." + BUTTON, that.element);

            if (target.hasClass(OVERFLOW_ANCHOR)) {
                return;
            }

            if (!target.length && that.popup) {
                target = $(e.target).closest("." + OVERFLOW_BUTTON, that.popup.container);
            }

            isDisabled = target.hasClass(OVERFLOW_BUTTON) ? target.parent("li").hasClass(STATE_DISABLED) : target.hasClass(STATE_DISABLED);

            if (isDisabled) {
                return;
            }

            if (e.target.closest("." + SPLIT_BUTTON_ARROW).length) {
                that._toggle(e);
                return;
            }

            id = target.attr("id") ? target.attr("id").replace(/(_overflow$)/, "") : undefined;

            if (target.hasClass(TOGGLE_BUTTON)) {
                group = target.data("group");
                handler = isFunction(target.data("toggle")) ? target.data("toggle") : null;

                that.toggle(target);
                isChecked = target.hasClass(STATE_ACTIVE);
                eventData = { target: target, group: group, checked: isChecked, id: id };

                if (handler) { handler.call(that, eventData); }
                that.trigger(TOGGLE, eventData);
            } else {
                handler = isFunction(target.data("click")) ? target.data("click") : null;
                eventData = { target: target, id: id };

                if (handler) { handler.call(that, eventData); }
                that.trigger(CLICK, eventData);
            }

            if (target.hasClass(OVERFLOW_BUTTON)) {
                that.popup.close();
            }

            splitContainer = target.closest(".k-split-container");
            if (splitContainer[0]) {
                popup = splitContainer.data("kendoPopup");
                (popup ? popup : splitContainer.parents(".km-popup-wrapper").data("kendoPopup")).close();
            }
        },

        _toggle: function (e) {
            var splitButton = $(e.target).closest("." + SPLIT_BUTTON),
                    popup = splitButton.data("kendoPopup"),
                    isDefaultPrevented;

            e.preventDefault();

            if (splitButton.hasClass(STATE_DISABLED)) {
                return;
            }

            if (popup.element.is(":visible")) {
                isDefaultPrevented = this.trigger(CLOSE, { target: splitButton });
            } else {
                isDefaultPrevented = this.trigger(OPEN, { target: splitButton });
            }

            if (!isDefaultPrevented) {
                popup.toggle();
            }
        },

        _toggleOverflow: function () {
            this.popup.toggle();
        },

        _resize: function (e) {
            var containerWidth = e.width;

            if (!this.options.resizable) {
                return;
            }

            this.popup.close();

            this._shrink(containerWidth);
            this._stretch(containerWidth);

            this._markVisibles();

            this._toggleOverflowAnchor();
        },

        _childrenWidth: function () {
            var childrenWidth = 0;

            this.element.children(":visible").each(function () {
                childrenWidth += $(this).outerWidth(true);
            });

            return Math.ceil(childrenWidth);
        },

        _shrink: function (containerWidth) {
            var commandElement,
                    visibleCommands;

            if (containerWidth < this._childrenWidth()) {
                visibleCommands = this.element.children(":visible:not([data-overflow='never'], ." + OVERFLOW_ANCHOR + ")");

                for (var i = visibleCommands.length - 1; i >= 0; i--) {
                    commandElement = visibleCommands.eq(i);
                    if (containerWidth > this._childrenWidth()) {
                        break;
                    } else {
                        this._hideItem(commandElement);
                    }
                }
            }
        },

        _stretch: function (containerWidth) {
            var commandElement,
                    hiddenCommands;

            if (containerWidth > this._childrenWidth()) {
                hiddenCommands = this.element.children(":hidden");

                for (var i = 0; i < hiddenCommands.length; i++) {
                    commandElement = hiddenCommands.eq(i);
                    if (containerWidth < this._childrenWidth() || !this._showItem(commandElement, containerWidth)) {
                        break;
                    }
                }
            }
        },

        _hideItem: function (item) {
            item.hide();
            if (this.popup) {
                this.popup.container
                        .find(">li[data-uid='" + item.data("uid") + "']")
                        .removeClass(OVERFLOW_HIDDEN);
            }
        },

        _showItem: function (item, containerWidth) {
            if (item.length && containerWidth > this._childrenWidth() + item.outerWidth(true)) {
                item.show();
                if (this.popup) {
                    this.popup.container
                            .find(">li[data-uid='" + item.data("uid") + "']")
                            .addClass(OVERFLOW_HIDDEN);
                }

                return true;
            }

            return false;
        },

        _markVisibles: function () {
            var overflowItems = this.popup.container.children(),
                    toolbarItems = this.element.children(":not(.k-overflow-anchor)"),
                    visibleOverflowItems = overflowItems.filter(":not(.k-overflow-hidden)"),
                    visibleToolbarItems = toolbarItems.filter(":visible");

            overflowItems.add(toolbarItems).removeClass(FIRST_TOOLBAR_VISIBLE + " " + LAST_TOOLBAR_VISIBLE);
            visibleOverflowItems.first().add(visibleToolbarItems.first()).addClass(FIRST_TOOLBAR_VISIBLE);
            visibleOverflowItems.last().add(visibleToolbarItems.last()).addClass(LAST_TOOLBAR_VISIBLE);
        }

    });

    kendo.ui.plugin(ToolBar);
})(window.kendo.jQuery);





(function ($, undefined) {

    var kendo = window.kendo,
        TimeView = kendo.TimeView,
        parse = kendo.parseDate,
        activeElement = kendo._activeElement,
        extractFormat = kendo._extractFormat,
        calendar = kendo.calendar,
        isInRange = calendar.isInRange,
        restrictValue = calendar.restrictValue,
        isEqualDatePart = calendar.isEqualDatePart,
        getMilliseconds = TimeView.getMilliseconds,
        ui = kendo.ui,
        Widget = ui.Widget,
        OPEN = "open",
        CLOSE = "close",
        CHANGE = "change",
        ns = ".kendoDateTimePicker",
        CLICK = "click" + ns,
        DISABLED = "disabled",
        READONLY = "readonly",
        DEFAULT = "k-state-default",
        FOCUSED = "k-state-focused",
        HOVER = "k-state-hover",
        STATEDISABLED = "k-state-disabled",
        HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns,
        MOUSEDOWN = "mousedown" + ns,
        MONTH = "month",
        SPAN = "<span/>",
        ARIA_ACTIVEDESCENDANT = "aria-activedescendant",
        ARIA_EXPANDED = "aria-expanded",
        ARIA_HIDDEN = "aria-hidden",
        ARIA_OWNS = "aria-owns",
        ARIA_DISABLED = "aria-disabled",
        ARIA_READONLY = "aria-readonly",
        DATE = Date,
        MIN = new DATE(1900, 0, 1),
        MAX = new DATE(2099, 11, 31),
        dateViewParams = { view: "date" },
        timeViewParams = { view: "time" },
        extend = $.extend;

    var DateTimePicker = Widget.extend({
        init: function (element, options) {
            var that = this, disabled;

            Widget.fn.init.call(that, element, options);

            element = that.element;
            options = that.options;

            options.min = parse(element.attr("min")) || parse(options.min);
            options.max = parse(element.attr("max")) || parse(options.max);

            normalize(options);

            that._wrapper();

            that._views();

            that._icons();

            that._reset();
            that._template();

            try {
                element[0].setAttribute("type", "text");
            } catch (e) {
                element[0].type = "text";
            }

            element.addClass("k-input")
                   .attr({
                       "role": "combobox",
                       "aria-expanded": false
                   });


            that._midnight = getMilliseconds(options.min) + getMilliseconds(options.max) === 0;

            disabled = element.is("[disabled]");
            if (disabled) {
                that.enable(false);
            } else {
                that.readonly(element.is("[readonly]"));
            }

            that._old = that._update(options.value || that.element.val());
            that._oldText = element.val();

            kendo.notify(that);
        },

        options: {
            name: "DateTimePicker",
            value: null,
            format: "",
            timeFormat: "",
            culture: "",
            parseFormats: [],
            dates: [],
            min: new DATE(MIN),
            max: new DATE(MAX),
            interval: 30,
            height: 200,
            footer: "",
            start: MONTH,
            depth: MONTH,
            animation: {},
            month: {},
            ARIATemplate: 'Current focused date is #=kendo.toString(data.current, "d")#'
        },

        events: [
        OPEN,
        CLOSE,
        CHANGE
    ],

        setOptions: function (options) {
            var that = this,
                value = that._value,
                dateViewOptions = that.dateView.options,
                timeViewOptions = that.timeView.options,
                min, max, currentValue;

            Widget.fn.setOptions.call(that, options);

            options = that.options;

            options.min = min = parse(options.min);
            options.max = max = parse(options.max);

            normalize(options);

            currentValue = options.value || that._value || that.dateView._current;

            if (min && !isEqualDatePart(min, currentValue)) {
                min = new DATE(MIN);
            }

            if (max && !isEqualDatePart(max, currentValue)) {
                max = new DATE(MAX);
            }

            that.dateView.setOptions(options);

            that.timeView.setOptions(extend({}, options, {
                format: options.timeFormat,
                min: min,
                max: max
            }));

            if (value) {
                that.element.val(kendo.toString(value, options.format, options.culture));
                that._updateARIA(value);
            }
        },

        _editable: function (options) {
            var that = this,
                element = that.element.off(ns),
                dateIcon = that._dateIcon.off(ns),
                timeIcon = that._timeIcon.off(ns),
                wrapper = that._inputWrapper.off(ns),
                readonly = options.readonly,
                disable = options.disable;

            if (!readonly && !disable) {
                wrapper
                    .addClass(DEFAULT)
                    .removeClass(STATEDISABLED)
                    .on(HOVEREVENTS, that._toggleHover);

                element.removeAttr(DISABLED)
                       .removeAttr(READONLY)
                       .attr(ARIA_DISABLED, false)
                       .attr(ARIA_READONLY, false)
                       .on("keydown" + ns, $.proxy(that._keydown, that))
                       .on("focus" + ns, function () {
                           that._inputWrapper.addClass(FOCUSED);
                       })
                       .on("focusout" + ns, function () {
                           that._inputWrapper.removeClass(FOCUSED);
                           if (element.val() !== that._oldText) {
                               that._change(element.val());
                           }
                           that.close("date");
                           that.close("time");
                       });

                dateIcon.on(MOUSEDOWN, preventDefault)
                        .on(CLICK, function () {
                            that.toggle("date");

                            if (!kendo.support.touch && element[0] !== activeElement()) {
                                element.focus();
                            }
                        });


                timeIcon.on(MOUSEDOWN, preventDefault)
                        .on(CLICK, function () {
                            that.toggle("time");

                            if (!kendo.support.touch && element[0] !== activeElement()) {
                                element.focus();
                            }
                        });

            } else {
                wrapper
                    .addClass(disable ? STATEDISABLED : DEFAULT)
                    .removeClass(disable ? DEFAULT : STATEDISABLED);

                element.attr(DISABLED, disable)
                       .attr(READONLY, readonly)
                       .attr(ARIA_DISABLED, disable)
                       .attr(ARIA_READONLY, readonly);
            }
        },

        readonly: function (readonly) {
            this._editable({
                readonly: readonly === undefined ? true : readonly,
                disable: false
            });
        },

        enable: function (enable) {
            this._editable({
                readonly: false,
                disable: !(enable = enable === undefined ? true : enable)
            });
        },

        destroy: function () {
            var that = this;

            Widget.fn.destroy.call(that);
            that.dateView.destroy();
            that.timeView.destroy();

            that.element.off(ns);
            that._dateIcon.off(ns);
            that._timeIcon.off(ns);
            that._inputWrapper.off(ns);

            if (that._form) {
                that._form.off("reset", that._resetHandler);
            }
        },

        close: function (view) {
            if (view !== "time") {
                view = "date";
            }

            this[view + "View"].close();
        },

        open: function (view) {
            if (view !== "time") {
                view = "date";
            }

            this[view + "View"].open();
        },

        min: function (value) {
            return this._option("min", value);
        },

        max: function (value) {
            return this._option("max", value);
        },

        toggle: function (view) {
            var secondView = "timeView";

            if (view !== "time") {
                view = "date";
            } else {
                secondView = "dateView";
            }

            this[view + "View"].toggle();
            this[secondView].close();
        },

        value: function (value) {
            var that = this;

            if (value === undefined) {
                return that._value;
            }

            that._old = that._update(value);
            if (that._old === null) {
                that.element.val("");
            }

            that._oldText = that.element.val();
        },

        _change: function (value) {
            var that = this;

            value = that._update(value);

            if (+that._old != +value) {
                that._old = value;
                that._oldText = that.element.val();

                that.trigger(CHANGE);

                // trigger the DOM change event so any subscriber gets notified
                that.element.trigger(CHANGE);
            }
        },

        _option: function (option, value) {
            var that = this;
            var options = that.options;
            var timeView = that.timeView;
            var timeViewOptions = timeView.options;
            var current = that._value || that._old;
            var minDateEqual;
            var maxDateEqual;

            if (value === undefined) {
                return options[option];
            }

            value = parse(value, options.parseFormats, options.culture);

            if (!value) {
                return;
            }

            if (options.min.getTime() === options.max.getTime()) {
                timeViewOptions.dates = [];
            }

            options[option] = new DATE(value.getTime());
            that.dateView[option](value);

            that._midnight = getMilliseconds(options.min) + getMilliseconds(options.max) === 0;

            if (current) {
                minDateEqual = isEqualDatePart(options.min, current);
                maxDateEqual = isEqualDatePart(options.max, current);
            }

            if (minDateEqual || maxDateEqual) {
                timeViewOptions[option] = value;

                if (minDateEqual && !maxDateEqual) {
                    timeViewOptions.max = lastTimeOption(options.interval);
                }

                if (maxDateEqual) {
                    if (that._midnight) {
                        timeView.dataBind([MAX]);
                        return;
                    } else if (!minDateEqual) {
                        timeViewOptions.min = MIN;
                    }
                }
            } else {
                timeViewOptions.max = MAX;
                timeViewOptions.min = MIN;
            }

            timeView.bind();
        },

        _toggleHover: function (e) {
            $(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
        },

        _update: function (value) {
            var that = this,
                options = that.options,
                min = options.min,
                max = options.max,
                dates = options.dates,
                timeView = that.timeView,
                current = that._value,
                date = parse(value, options.parseFormats, options.culture),
                isSameType = (date === null && current === null) || (date instanceof Date && current instanceof Date),
                rebind, timeViewOptions, old, skip, formattedValue;

            if (+date === +current && isSameType) {
                formattedValue = kendo.toString(date, options.format, options.culture);

                if (formattedValue !== value) {
                    that.element.val(date === null ? value : formattedValue);
                }

                return date;
            }

            if (date !== null && isEqualDatePart(date, min)) {
                date = restrictValue(date, min, max);
            } else if (!isInRange(date, min, max)) {
                date = null;
            }

            that._value = date;
            timeView.value(date);
            that.dateView.value(date);

            if (date) {
                old = that._old;
                timeViewOptions = timeView.options;

                if (dates[0]) {
                    dates = $.grep(dates, function (d) { return isEqualDatePart(date, d); });

                    if (dates[0]) {
                        timeView.dataBind(dates);
                        skip = true;
                    }
                }

                if (!skip) {
                    if (isEqualDatePart(date, min)) {
                        timeViewOptions.min = min;
                        timeViewOptions.max = lastTimeOption(options.interval);
                        rebind = true;
                    }

                    if (isEqualDatePart(date, max)) {
                        if (that._midnight) {
                            timeView.dataBind([MAX]);
                            skip = true;
                        } else {
                            timeViewOptions.max = max;
                            if (!rebind) {
                                timeViewOptions.min = MIN;
                            }
                            rebind = true;
                        }
                    }
                }

                if (!skip && ((!old && rebind) || (old && !isEqualDatePart(old, date)))) {
                    if (!rebind) {
                        timeViewOptions.max = MAX;
                        timeViewOptions.min = MIN;
                    }

                    timeView.bind();
                }
            }

            that.element.val(date ? kendo.toString(date, options.format, options.culture) : value);
            that._updateARIA(date);

            return date;
        },

        _keydown: function (e) {
            var that = this,
                dateView = that.dateView,
                timeView = that.timeView,
                value = that.element.val(),
                isDateViewVisible = dateView.popup.visible();

            if (e.altKey && e.keyCode === kendo.keys.DOWN) {
                that.toggle(isDateViewVisible ? "time" : "date");
            } else if (isDateViewVisible) {
                dateView.move(e);
                that._updateARIA(dateView._current);
            } else if (timeView.popup.visible()) {
                timeView.move(e);
            } else if (e.keyCode === kendo.keys.ENTER && value !== that._oldText) {
                that._change(value);
            }
        },

        _views: function () {
            var that = this,
                element = that.element,
                options = that.options,
                id = element.attr("id"),
                dateView, timeView,
                div, ul, msMin,
                date;

            that.dateView = dateView = new kendo.DateView(extend({}, options, {
                id: id,
                anchor: that.wrapper,
                change: function () {
                    var value = dateView.calendar.value(),
                        msValue = +value,
                        msMin = +options.min,
                        msMax = +options.max,
                        current;

                    if (msValue === msMin || msValue === msMax) {
                        current = new DATE(+that._value);
                        current.setFullYear(value.getFullYear(), value.getMonth(), value.getDate());

                        if (isInRange(current, msMin, msMax)) {
                            value = current;
                        }
                    }

                    that._change(value);
                    that.close("date");
                },
                close: function (e) {
                    if (that.trigger(CLOSE, dateViewParams)) {
                        e.preventDefault();
                    } else {
                        element.attr(ARIA_EXPANDED, false);
                        div.attr(ARIA_HIDDEN, true);

                        if (!timeView.popup.visible()) {
                            element.removeAttr(ARIA_OWNS);
                        }
                    }
                },
                open: function (e) {
                    if (that.trigger(OPEN, dateViewParams)) {
                        e.preventDefault();
                    } else {

                        if (that.element.val() !== that._oldText) {
                            date = parse(element.val(), options.parseFormats, options.culture);

                            that.dateView[date ? "current" : "value"](date);
                        }

                        div.attr(ARIA_HIDDEN, false);
                        element.attr(ARIA_EXPANDED, true)
                               .attr(ARIA_OWNS, dateView._dateViewID);

                        that._updateARIA(date);
                    }
                }
            }));
            div = dateView.div;

            msMin = options.min.getTime();
            that.timeView = timeView = new TimeView({
                id: id,
                value: options.value,
                anchor: that.wrapper,
                animation: options.animation,
                format: options.timeFormat,
                culture: options.culture,
                height: options.height,
                interval: options.interval,
                min: new DATE(MIN),
                max: new DATE(MAX),
                dates: msMin === options.max.getTime() ? [new Date(msMin)] : [],
                parseFormats: options.parseFormats,
                change: function (value, trigger) {
                    value = timeView._parse(value);

                    if (value < options.min) {
                        value = new DATE(+options.min);
                        timeView.options.min = value;
                    } else if (value > options.max) {
                        value = new DATE(+options.max);
                        timeView.options.max = value;
                    }

                    if (trigger) {
                        that._timeSelected = true;
                        that._change(value);
                    } else {
                        element.val(kendo.toString(value, options.format, options.culture));
                        dateView.value(value);
                        that._updateARIA(value);
                    }
                },
                close: function (e) {
                    if (that.trigger(CLOSE, timeViewParams)) {
                        e.preventDefault();
                    } else {
                        ul.attr(ARIA_HIDDEN, true);
                        element.attr(ARIA_EXPANDED, false);

                        if (!dateView.popup.visible()) {
                            element.removeAttr(ARIA_OWNS);
                        }
                    }
                },
                open: function (e) {
                    timeView._adjustListWidth();
                    if (that.trigger(OPEN, timeViewParams)) {
                        e.preventDefault();
                    } else {
                        ul.attr(ARIA_HIDDEN, false);
                        element.attr(ARIA_EXPANDED, true)
                               .attr(ARIA_OWNS, timeView._timeViewID);

                        timeView.options.active(timeView.current());
                    }
                },
                active: function (current) {
                    element.removeAttr(ARIA_ACTIVEDESCENDANT);
                    if (current) {
                        element.attr(ARIA_ACTIVEDESCENDANT, timeView._optionID);
                    }
                }
            });
            ul = timeView.ul;
        },

        _icons: function () {
            var that = this,
                element = that.element,
                icons;

            icons = element.next("span.k-select");

            if (!icons[0]) {
                icons = $('<span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-calendar">select</span><span unselectable="on" class="k-icon k-i-clock">select</span></span>').insertAfter(element);
            }

            icons = icons.children();
            that._dateIcon = icons.eq(0).attr({
                "role": "button",
                "aria-controls": that.dateView._dateViewID
            });

            that._timeIcon = icons.eq(1).attr({
                "role": "button",
                "aria-controls": that.timeView._timeViewID
            });
        },

        _wrapper: function () {
            var that = this,
            element = that.element,
            wrapper;

            wrapper = element.parents(".k-datetimepicker");

            if (!wrapper[0]) {
                wrapper = element.wrap(SPAN).parent().addClass("k-picker-wrap k-state-default");
                wrapper = wrapper.wrap(SPAN).parent();
            }

            wrapper[0].style.cssText = element[0].style.cssText;
            element.css({
                width: "100%",
                height: element[0].style.height
            });

            that.wrapper = wrapper.addClass("k-widget k-datetimepicker k-header")
                                  .addClass(element[0].className);

            that._inputWrapper = $(wrapper[0].firstChild);
        },

        _reset: function () {
            var that = this,
                element = that.element,
                formId = element.attr("form"),
                form = formId ? $("#" + formId) : element.closest("form");

            if (form[0]) {
                that._resetHandler = function () {
                    that.value(element[0].defaultValue);
                };

                that._form = form.on("reset", that._resetHandler);
            }
        },

        _template: function () {
            this._ariaTemplate = kendo.template(this.options.ARIATemplate);
        },

        _updateARIA: function (date) {
            var cell;
            var that = this;
            var calendar = that.dateView.calendar;

            that.element.removeAttr(ARIA_ACTIVEDESCENDANT);

            if (calendar) {
                cell = calendar._cell;
                cell.attr("aria-label", that._ariaTemplate({ current: date || calendar.current() }));

                that.element.attr(ARIA_ACTIVEDESCENDANT, cell.attr("id"));
            }
        }
    });

    function lastTimeOption(interval) {
        var date = new Date(2100, 0, 1);
        date.setMinutes(-interval);
        return date;
    }

    function preventDefault(e) {
        e.preventDefault();
    }

    function normalize(options) {
        var patterns = kendo.getCulture(options.culture).calendars.standard.patterns,
            timeFormat;

        options.format = extractFormat(options.format || patterns.g);
        options.timeFormat = timeFormat = extractFormat(options.timeFormat || patterns.t);
        kendo.DateView.normalize(options);
        if ($.inArray(timeFormat, options.parseFormats) === -1) {
            options.parseFormats.splice(1, 0, timeFormat);
        }
    }

    ui.plugin(DateTimePicker);

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        keys = kendo.keys,
        extend = $.extend,
        proxy = $.proxy,
        Widget = ui.Widget,
        pxUnitsRegex = /^\d+(\.\d+)?px$/i,
        percentageUnitsRegex = /^\d+(\.\d+)?%$/i,
        NS = ".kendoSplitter",
        EXPAND = "expand",
        COLLAPSE = "collapse",
        CONTENTLOAD = "contentLoad",
        ERROR = "error",
        RESIZE = "resize",
        LAYOUTCHANGE = "layoutChange",
        HORIZONTAL = "horizontal",
        VERTICAL = "vertical",
        MOUSEENTER = "mouseenter",
        CLICK = "click",
        PANE = "pane",
        MOUSELEAVE = "mouseleave",
        FOCUSED = "k-state-focused",
        KPANE = "k-" + PANE,
        PANECLASS = "." + KPANE;

    function isPercentageSize(size) {
        return percentageUnitsRegex.test(size);
    }

    function isPixelSize(size) {
        return pxUnitsRegex.test(size) || /^\d+$/.test(size);
    }

    function isFluid(size) {
        return !isPercentageSize(size) && !isPixelSize(size);
    }

    function calculateSize(size, total) {
        var output = parseInt(size, 10);

        if (isPercentageSize(size)) {
            output = Math.floor(output * total / 100);
        }

        return output;
    }

    function panePropertyAccessor(propertyName, triggersResize) {
        return function (pane, value) {
            var paneConfig = this.element.find(pane).data(PANE);

            if (arguments.length == 1) {
                return paneConfig[propertyName];
            }

            paneConfig[propertyName] = value;

            if (triggersResize) {
                var splitter = this.element.data("kendo" + this.options.name);
                splitter.resize(true);
            }
        };
    }

    var Splitter = Widget.extend({
        init: function (element, options) {
            var that = this,
                isHorizontal;

            Widget.fn.init.call(that, element, options);

            that.wrapper = that.element;

            isHorizontal = that.options.orientation.toLowerCase() != VERTICAL;
            that.orientation = isHorizontal ? HORIZONTAL : VERTICAL;
            that._dimension = isHorizontal ? "width" : "height";
            that._keys = {
                decrease: isHorizontal ? keys.LEFT : keys.UP,
                increase: isHorizontal ? keys.RIGHT : keys.DOWN
            };

            that._resizeStep = 10;

            that._marker = kendo.guid().substring(0, 8);

            that._initPanes();

            that.resizing = new PaneResizing(that);

            that.element.triggerHandler("init" + NS);
        },
        events: [
            EXPAND,
            COLLAPSE,
            CONTENTLOAD,
            ERROR,
            RESIZE,
            LAYOUTCHANGE
        ],

        _attachEvents: function () {
            var that = this,
                orientation = that.options.orientation;

            // do not use delegated events to increase performance of nested elements
            that.element
                .children(".k-splitbar-draggable-" + orientation)
                    .on("keydown" + NS, $.proxy(that._keydown, that))
                    .on("mousedown" + NS, function (e) { e.currentTarget.focus(); })
                    .on("focus" + NS, function (e) { $(e.currentTarget).addClass(FOCUSED); })
                    .on("blur" + NS, function (e) {
                        $(e.currentTarget).removeClass(FOCUSED);
                        if (that.resizing) {
                            that.resizing.end();
                        }
                    })
                    .on(MOUSEENTER + NS, function () { $(this).addClass("k-splitbar-" + that.orientation + "-hover"); })
                    .on(MOUSELEAVE + NS, function () { $(this).removeClass("k-splitbar-" + that.orientation + "-hover"); })
                    .on("mousedown" + NS, function () { that._panes().append("<div class='k-splitter-overlay k-overlay' />"); })
                    .on("mouseup" + NS, function () { that._panes().children(".k-splitter-overlay").remove(); })
                .end()
                .children(".k-splitbar")
                    .on("dblclick" + NS, proxy(that._togglePane, that))
                    .children(".k-collapse-next, .k-collapse-prev").on(CLICK + NS, that._arrowClick(COLLAPSE)).end()
                    .children(".k-expand-next, .k-expand-prev").on(CLICK + NS, that._arrowClick(EXPAND)).end()
                .end();

            $(window).on("resize" + NS + that._marker, proxy(that.resize, that));
        },

        _detachEvents: function () {
            var that = this;

            that.element
                .children(".k-splitbar-draggable-" + that.orientation).off(NS).end()
                .children(".k-splitbar").off("dblclick" + NS)
                    .children(".k-collapse-next, .k-collapse-prev, .k-expand-next, .k-expand-prev").off(NS);

            $(window).off("resize" + NS + that._marker);
        },

        options: {
            name: "Splitter",
            orientation: HORIZONTAL,
            panes: []
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            this._detachEvents();

            if (this.resizing) {
                this.resizing.destroy();
            }

            kendo.destroy(this.element);

            this.wrapper = this.element = null;
        },

        _keydown: function (e) {
            var that = this,
                key = e.keyCode,
                resizing = that.resizing,
                target = $(e.currentTarget),
                navigationKeys = that._keys,
                increase = key === navigationKeys.increase,
                decrease = key === navigationKeys.decrease,
                pane;

            if (increase || decrease) {
                if (e.ctrlKey) {
                    pane = target[decrease ? "next" : "prev"]();

                    if (resizing && resizing.isResizing()) {
                        resizing.end();
                    }

                    if (!pane[that._dimension]()) {
                        that._triggerAction(EXPAND, pane);
                    } else {
                        that._triggerAction(COLLAPSE, target[decrease ? "prev" : "next"]());
                    }
                } else if (resizing) {
                    resizing.move((decrease ? -1 : 1) * that._resizeStep, target);
                }
                e.preventDefault();
            } else if (key === keys.ENTER && resizing) {
                resizing.end();
                e.preventDefault();
            }
        },

        _initPanes: function () {
            var panesConfig = this.options.panes || [];
            var that = this;

            this.element
                .addClass("k-widget").addClass("k-splitter")
                .children()
                    .each(function (i, pane) {
                        if (pane.nodeName.toLowerCase() != "script") {
                            that._initPane(pane, panesConfig[i]);
                        }
                    });

            this.resize();
        },

        _initPane: function (pane, config) {
            pane = $(pane)
                .attr("role", "group")
                .addClass(KPANE);

            pane.data(PANE, config ? config : {})
                .toggleClass("k-scrollable", config ? config.scrollable !== false : true);

            this.ajaxRequest(pane);
        },

        ajaxRequest: function (pane, url, data) {
            var that = this,
                paneConfig;

            pane = that.element.find(pane);
            paneConfig = pane.data(PANE);

            url = url || paneConfig.contentUrl;

            if (url) {
                pane.append("<span class='k-icon k-loading k-pane-loading' />");

                if (kendo.isLocalUrl(url)) {
                    jQuery.ajax({
                        url: url,
                        data: data || {},
                        type: "GET",
                        dataType: "html",
                        success: function (data) {
                            that.angular("cleanup", function () { return { elements: pane.get() }; });
                            pane.html(data);
                            that.angular("compile", function () { return { elements: pane.get() }; });

                            that.trigger(CONTENTLOAD, { pane: pane[0] });
                        },
                        error: function (xhr, status) {
                            that.trigger(ERROR, {
                                pane: pane[0],
                                status: status,
                                xhr: xhr
                            });
                        }
                    });
                } else {
                    pane.removeClass("k-scrollable")
                        .html("<iframe src='" + url + "' frameborder='0' class='k-content-frame'>" +
                                "This page requires frames in order to show content" +
                              "</iframe>");
                }
            }
        },

        _triggerAction: function (type, pane) {
            if (!this.trigger(type, { pane: pane[0] })) {
                this[type](pane[0]);
            }
        },

        _togglePane: function (e) {
            var that = this,
                target = $(e.target),
                arrow;

            if (target.closest(".k-splitter")[0] != that.element[0]) {
                return;
            }

            arrow = target.children(".k-icon:not(.k-resize-handle)");

            if (arrow.length !== 1) {
                return;
            }

            if (arrow.is(".k-collapse-prev")) {
                that._triggerAction(COLLAPSE, target.prev());
            } else if (arrow.is(".k-collapse-next")) {
                that._triggerAction(COLLAPSE, target.next());
            } else if (arrow.is(".k-expand-prev")) {
                that._triggerAction(EXPAND, target.prev());
            } else if (arrow.is(".k-expand-next")) {
                that._triggerAction(EXPAND, target.next());
            }
        },
        _arrowClick: function (arrowType) {
            var that = this;

            return function (e) {
                var target = $(e.target),
                    pane;

                if (target.closest(".k-splitter")[0] != that.element[0]) {
                    return;
                }

                if (target.is(".k-" + arrowType + "-prev")) {
                    pane = target.parent().prev();
                } else {
                    pane = target.parent().next();
                }
                that._triggerAction(arrowType, pane);
            };
        },
        _updateSplitBar: function (splitbar, previousPane, nextPane) {
            var catIconIf = function (iconType, condition) {
                return condition ? "<div class='k-icon " + iconType + "' />" : "";
            },
                orientation = this.orientation,
                draggable = (previousPane.resizable !== false) && (nextPane.resizable !== false),
                prevCollapsible = previousPane.collapsible,
                prevCollapsed = previousPane.collapsed,
                nextCollapsible = nextPane.collapsible,
                nextCollapsed = nextPane.collapsed;

            splitbar.addClass("k-splitbar k-state-default k-splitbar-" + orientation)
                    .attr("role", "separator")
                    .attr("aria-expanded", !(prevCollapsed || nextCollapsed))
                    .removeClass("k-splitbar-" + orientation + "-hover")
                    .toggleClass("k-splitbar-draggable-" + orientation,
                        draggable && !prevCollapsed && !nextCollapsed)
                    .toggleClass("k-splitbar-static-" + orientation,
                        !draggable && !prevCollapsible && !nextCollapsible)
                    .html(
                        catIconIf("k-collapse-prev", prevCollapsible && !prevCollapsed && !nextCollapsed) +
                        catIconIf("k-expand-prev", prevCollapsible && prevCollapsed && !nextCollapsed) +
                        catIconIf("k-resize-handle", draggable) +
                        catIconIf("k-collapse-next", nextCollapsible && !nextCollapsed && !prevCollapsed) +
                        catIconIf("k-expand-next", nextCollapsible && nextCollapsed && !prevCollapsed)
                    );

            if (!draggable && !prevCollapsible && !nextCollapsible) {
                splitbar.removeAttr("tabindex");
            }
        },
        _updateSplitBars: function () {
            var that = this;

            this.element.children(".k-splitbar").each(function () {
                var splitbar = $(this),
                    previousPane = splitbar.prevAll(PANECLASS).first().data(PANE),
                    nextPane = splitbar.nextAll(PANECLASS).first().data(PANE);

                if (!nextPane) {
                    return;
                }

                that._updateSplitBar(splitbar, previousPane, nextPane);
            });
        },
        _removeSplitBars: function () {
            this.element.children(".k-splitbar").remove();
        },
        _panes: function () {
            return this.element.children(PANECLASS);
        },

        _resize: function () {
            var that = this,
                element = that.element,
                panes = element.children(PANECLASS),
                isHorizontal = that.orientation == HORIZONTAL,
                splitBars = element.children(".k-splitbar"),
                splitBarsCount = splitBars.length,
                sizingProperty = isHorizontal ? "width" : "height",
                totalSize = element[sizingProperty]();

            if (splitBarsCount === 0) {
                splitBarsCount = panes.length - 1;
                panes.slice(0, splitBarsCount)
                     .after("<div tabindex='0' class='k-splitbar' data-marker='" + that._marker + "' />");

                that._updateSplitBars();
                splitBars = element.children(".k-splitbar");
            } else {
                that._updateSplitBars();
            }

            // discard splitbar sizes from total size
            splitBars.each(function () {
                totalSize -= this[isHorizontal ? "offsetWidth" : "offsetHeight"];
            });

            var sizedPanesWidth = 0,
                sizedPanesCount = 0,
                freeSizedPanes = $();

            panes.css({ position: "absolute", top: 0 })
                [sizingProperty](function () {
                    var element = $(this),
                        config = element.data(PANE) || {}, size;

                    element.removeClass("k-state-collapsed");
                    if (config.collapsed) {
                        size = config.collapsedSize ? calculateSize(config.collapsedSize, totalSize) : 0;
                        element.css("overflow", "hidden").addClass("k-state-collapsed");
                    } else if (isFluid(config.size)) {
                        freeSizedPanes = freeSizedPanes.add(this);
                        return;
                    } else { // sized in px/%, not collapsed
                        size = calculateSize(config.size, totalSize);
                    }

                    sizedPanesCount++;
                    sizedPanesWidth += size;

                    return size;
                });

            totalSize -= sizedPanesWidth;

            var freeSizePanesCount = freeSizedPanes.length,
                freeSizePaneWidth = Math.floor(totalSize / freeSizePanesCount);

            freeSizedPanes
                .slice(0, freeSizePanesCount - 1)
                    .css(sizingProperty, freeSizePaneWidth)
                .end()
                .eq(freeSizePanesCount - 1)
                    .css(sizingProperty, totalSize - (freeSizePanesCount - 1) * freeSizePaneWidth);

            // arrange panes
            var sum = 0,
                alternateSizingProperty = isHorizontal ? "height" : "width",
                positioningProperty = isHorizontal ? "left" : "top",
                sizingDomProperty = isHorizontal ? "offsetWidth" : "offsetHeight";

            if (freeSizePanesCount === 0) {
                var lastNonCollapsedPane = panes.filter(function () {
                    return !(($(this).data(PANE) || {}).collapsed);
                }).last();

                lastNonCollapsedPane[sizingProperty](totalSize + lastNonCollapsedPane[0][sizingDomProperty]);
            }

            element.children()
                .css(alternateSizingProperty, element[alternateSizingProperty]())
                .each(function (i, child) {
                    if (child.tagName.toLowerCase() != "script") {
                        child.style[positioningProperty] = Math.floor(sum) + "px";
                        sum += child[sizingDomProperty];
                    }
                });

            that._detachEvents();
            that._attachEvents();

            kendo.resize(panes);
            that.trigger(LAYOUTCHANGE);
        },

        toggle: function (pane, expand) {
            var that = this,
                paneConfig;

            pane = that.element.find(pane);
            paneConfig = pane.data(PANE);

            if (!expand && !paneConfig.collapsible) {
                return;
            }

            if (arguments.length == 1) {
                expand = paneConfig.collapsed === undefined ? false : paneConfig.collapsed;
            }

            paneConfig.collapsed = !expand;

            if (paneConfig.collapsed) {
                pane.css("overflow", "hidden");
            } else {
                pane.css("overflow", "");
            }

            that.resize(true);
        },

        collapse: function (pane) {
            this.toggle(pane, false);
        },

        expand: function (pane) {
            this.toggle(pane, true);
        },

        _addPane: function (config, idx, paneElement) {
            var that = this;

            if (paneElement.length) {
                that.options.panes.splice(idx, 0, config);
                that._initPane(paneElement, config);

                that._removeSplitBars();

                that.resize(true);
            }

            return paneElement;
        },

        append: function (config) {
            config = config || {};

            var that = this,
                paneElement = $("<div />").appendTo(that.element);

            return that._addPane(config, that.options.panes.length, paneElement);
        },

        insertBefore: function (config, referencePane) {
            referencePane = $(referencePane);
            config = config || {};

            var that = this,
                idx = that.wrapper.children(".k-pane").index(referencePane),
                paneElement = $("<div />").insertBefore($(referencePane));

            return that._addPane(config, idx, paneElement);
        },

        insertAfter: function (config, referencePane) {
            referencePane = $(referencePane);
            config = config || {};

            var that = this,
                idx = that.wrapper.children(".k-pane").index(referencePane),
                paneElement = $("<div />").insertAfter($(referencePane));

            return that._addPane(config, idx + 1, paneElement);
        },

        remove: function (pane) {
            pane = $(pane);

            var that = this;

            if (pane.length) {
                kendo.destroy(pane);
                pane.each(function (idx, element) {
                    that.options.panes.splice(that.wrapper.children(".k-pane").index(element), 1);
                    $(element).remove();
                });

                that._removeSplitBars();

                if (that.options.panes.length) {
                    that.resize(true);
                }
            }

            return that;
        },

        size: panePropertyAccessor("size", true),

        min: panePropertyAccessor("min"),

        max: panePropertyAccessor("max")
    });

    ui.plugin(Splitter);

    var verticalDefaults = {
        sizingProperty: "height",
        sizingDomProperty: "offsetHeight",
        alternateSizingProperty: "width",
        positioningProperty: "top",
        mousePositioningProperty: "pageY"
    };

    var horizontalDefaults = {
        sizingProperty: "width",
        sizingDomProperty: "offsetWidth",
        alternateSizingProperty: "height",
        positioningProperty: "left",
        mousePositioningProperty: "pageX"
    };

    function PaneResizing(splitter) {
        var that = this,
            orientation = splitter.orientation;

        that.owner = splitter;
        that._element = splitter.element;
        that.orientation = orientation;

        extend(that, orientation === HORIZONTAL ? horizontalDefaults : verticalDefaults);

        that._resizable = new kendo.ui.Resizable(splitter.element, {
            orientation: orientation,
            handle: ".k-splitbar-draggable-" + orientation + "[data-marker=" + splitter._marker + "]",
            hint: proxy(that._createHint, that),
            start: proxy(that._start, that),
            max: proxy(that._max, that),
            min: proxy(that._min, that),
            invalidClass: "k-restricted-size-" + orientation,
            resizeend: proxy(that._stop, that)
        });
    }

    PaneResizing.prototype = {
        press: function (target) {
            this._resizable.press(target);
        },

        move: function (delta, target) {
            if (!this.pressed) {
                this.press(target);
                this.pressed = true;
            }

            if (!this._resizable.target) {
                this._resizable.press(target);
            }

            this._resizable.move(delta);
        },

        end: function () {
            this._resizable.end();
            this.pressed = false;
        },

        destroy: function () {
            this._resizable.destroy();
            this._resizable = this._element = this.owner = null;
        },

        isResizing: function () {
            return this._resizable.resizing;
        },

        _createHint: function (handle) {
            var that = this;
            return $("<div class='k-ghost-splitbar k-ghost-splitbar-" + that.orientation + " k-state-default' />")
                        .css(that.alternateSizingProperty, handle[that.alternateSizingProperty]());
        },

        _start: function (e) {
            var that = this,
                splitbar = $(e.currentTarget),
                previousPane = splitbar.prev(),
                nextPane = splitbar.next(),
                previousPaneConfig = previousPane.data(PANE),
                nextPaneConfig = nextPane.data(PANE),
                prevBoundary = parseInt(previousPane[0].style[that.positioningProperty], 10),
                nextBoundary = parseInt(nextPane[0].style[that.positioningProperty], 10) + nextPane[0][that.sizingDomProperty] - splitbar[0][that.sizingDomProperty],
                totalSize = parseInt(that._element.css(that.sizingProperty), 10),
                toPx = function (value) {
                    var val = parseInt(value, 10);
                    return (isPixelSize(value) ? val : (totalSize * val) / 100) || 0;
                },
                prevMinSize = toPx(previousPaneConfig.min),
                prevMaxSize = toPx(previousPaneConfig.max) || nextBoundary - prevBoundary,
                nextMinSize = toPx(nextPaneConfig.min),
                nextMaxSize = toPx(nextPaneConfig.max) || nextBoundary - prevBoundary;

            that.previousPane = previousPane;
            that.nextPane = nextPane;
            that._maxPosition = Math.min(nextBoundary - nextMinSize, prevBoundary + prevMaxSize);
            that._minPosition = Math.max(prevBoundary + prevMinSize, nextBoundary - nextMaxSize);
        },
        _max: function () {
            return this._maxPosition;
        },
        _min: function () {
            return this._minPosition;
        },
        _stop: function (e) {
            var that = this,
                splitbar = $(e.currentTarget),
                owner = that.owner;

            owner._panes().children(".k-splitter-overlay").remove();

            if (e.keyCode !== kendo.keys.ESC) {
                var ghostPosition = e.position,
                    previousPane = splitbar.prev(),
                    nextPane = splitbar.next(),
                    previousPaneConfig = previousPane.data(PANE),
                    nextPaneConfig = nextPane.data(PANE),
                    previousPaneNewSize = ghostPosition - parseInt(previousPane[0].style[that.positioningProperty], 10),
                    nextPaneNewSize = parseInt(nextPane[0].style[that.positioningProperty], 10) + nextPane[0][that.sizingDomProperty] - ghostPosition - splitbar[0][that.sizingDomProperty],
                    fluidPanesCount = that._element.children(PANECLASS).filter(function () { return isFluid($(this).data(PANE).size); }).length;

                if (!isFluid(previousPaneConfig.size) || fluidPanesCount > 1) {
                    if (isFluid(previousPaneConfig.size)) {
                        fluidPanesCount--;
                    }

                    previousPaneConfig.size = previousPaneNewSize + "px";
                }

                if (!isFluid(nextPaneConfig.size) || fluidPanesCount > 1) {
                    nextPaneConfig.size = nextPaneNewSize + "px";
                }

                owner.resize(true);
            }

            return false;
        }
    };

})(window.kendo.jQuery);





(function ($) {
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        keys = kendo.keys,
        NS = ".kendoSchedulerView",
        math = Math;

    function levels(values, key) {
        var result = [];

        function collect(depth, values) {
            values = values[key];

            if (values) {
                var level = result[depth] = result[depth] || [];

                for (var idx = 0; idx < values.length; idx++) {
                    level.push(values[idx]);
                    collect(depth + 1, values[idx]);
                }
            }
        }

        collect(0, values);

        return result;
    }

    function cellspacing() {
        if (kendo.support.cssBorderSpacing) {
            return "";
        }

        return 'cellspacing="0"';
    }

    function table(tableRows, className) {
        if (!tableRows.length) {
            return "";
        }

        return '<table ' + cellspacing() + ' class="' + $.trim('k-scheduler-table ' + (className || "")) + '">' +
               '<tr>' +
                    tableRows.join("</tr><tr>") +
               '</tr>' +
               '</table>';
    }

    function allDayTable(tableRows, className) {
        if (!tableRows.length) {
            return "";
        }

        return "<div style='position:relative'>" + table(tableRows, className) + "</div>";
    }

    function timesHeader(columnLevelCount, allDaySlot, rowCount) {
        var tableRows = [];

        if (rowCount > 0) {
            for (var idx = 0; idx < columnLevelCount; idx++) {
                tableRows.push("<th>&nbsp;</th>");
            }
        }

        if (allDaySlot) {
            tableRows.push('<th class="k-scheduler-times-all-day">' + allDaySlot.text + '</th>');
        }

        if (rowCount < 1) {
            return $();
        }

        return $('<div class="k-scheduler-times">' + table(tableRows) + '</div>');
    }

    function datesHeader(columnLevels, columnCount, allDaySlot) {
        var dateTableRows = [];
        var columnIndex;

        for (var columnLevelIndex = 0; columnLevelIndex < columnLevels.length; columnLevelIndex++) {
            var level = columnLevels[columnLevelIndex];
            var th = [];
            var colspan = columnCount / level.length;

            for (columnIndex = 0; columnIndex < level.length; columnIndex++) {
                var column = level[columnIndex];

                th.push('<th colspan="' + (column.colspan || colspan) + '" class="' + (column.className || "") + '">' + column.text + "</th>");
            }

            dateTableRows.push(th.join(""));
        }

        var allDayTableRows = [];

        if (allDaySlot) {
            var lastLevel = columnLevels[columnLevels.length - 1];
            var td = [];
            var cellContent = allDaySlot.cellContent;

            for (columnIndex = 0; columnIndex < lastLevel.length; columnIndex++) {
                td.push('<td class="' + (lastLevel[columnIndex].className || "") + '">' + (cellContent ? cellContent(columnIndex) : '&nbsp;') + '</th>');
            }

            allDayTableRows.push(td.join(""));
        }

        return $(
            '<div class="k-scheduler-header k-state-default">' +
                '<div class="k-scheduler-header-wrap">' +
                    table(dateTableRows) +
                    allDayTable(allDayTableRows, "k-scheduler-header-all-day") +
                '</div>' +
            '</div>'
        );
    }

    function times(rowLevels, rowCount) {
        var rows = new Array(rowCount).join().split(",");
        var rowHeaderRows = [];
        var rowIndex;

        for (var rowLevelIndex = 0; rowLevelIndex < rowLevels.length; rowLevelIndex++) {
            var level = rowLevels[rowLevelIndex];
            var rowspan = rowCount / level.length;
            var className;

            for (rowIndex = 0; rowIndex < level.length; rowIndex++) {
                className = level[rowIndex].className || "";

                if (level[rowIndex].allDay) {
                    className = "k-scheduler-times-all-day";
                }

                rows[rowspan * rowIndex] += '<th class="' + className + '" rowspan="' + rowspan + '">' + level[rowIndex].text + "</th>";
            }
        }

        for (rowIndex = 0; rowIndex < rowCount; rowIndex++) {
            rowHeaderRows.push(rows[rowIndex]);
        }

        if (rowCount < 1) {
            return $();
        }

        return $('<div class="k-scheduler-times">' + table(rowHeaderRows) + '</div>');
    }

    function content() {
        return $(
            '<div class="k-scheduler-content">' +
                '<table ' + cellspacing() + ' class="k-scheduler-table"/>' +
            '</div>'
        );
    }
    var HINT = '<div class="k-marquee k-scheduler-marquee">' +
                    '<div class="k-marquee-color"></div>' +
                    '<div class="k-marquee-text">' +
                        '<div class="k-label-top"></div>' +
                        '<div class="k-label-bottom"></div>' +
                    '</div>' +
                '</div>';

    kendo.ui.scheduler = {};

    var ResourceView = kendo.Class.extend({
        init: function (index) {
            this._index = index;
            this._timeSlotCollections = [];
            this._daySlotCollections = [];
        },

        addTimeSlotCollection: function (startDate, endDate) {
            return this._addCollection(startDate, endDate, this._timeSlotCollections);
        },

        addDaySlotCollection: function (startDate, endDate) {
            return this._addCollection(startDate, endDate, this._daySlotCollections);
        },

        _addCollection: function (startDate, endDate, collections) {
            var collection = new SlotCollection(startDate, endDate, this._index, collections.length);

            collections.push(collection);

            return collection;
        },

        timeSlotCollectionCount: function () {
            return this._timeSlotCollections.length;
        },

        daySlotCollectionCount: function () {
            return this._daySlotCollections.length;
        },

        daySlotByPosition: function (x, y) {
            return this._slotByPosition(x, y, this._daySlotCollections);
        },

        timeSlotByPosition: function (x, y) {
            return this._slotByPosition(x, y, this._timeSlotCollections);
        },

        _slotByPosition: function (x, y, collections) {
            for (var collectionIndex = 0; collectionIndex < collections.length; collectionIndex++) {
                var collection = collections[collectionIndex];

                for (var slotIndex = 0; slotIndex < collection.count(); slotIndex++) {
                    var slot = collection.at(slotIndex);

                    if (x >= slot.offsetLeft && x < slot.offsetLeft + slot.clientWidth &&
                       y >= slot.offsetTop && y <= slot.offsetTop + slot.clientHeight) {
                        return slot;
                    }
                }
            }
        },

        refresh: function () {
            var collectionIndex;

            for (collectionIndex = 0; collectionIndex < this._daySlotCollections.length; collectionIndex++) {
                this._daySlotCollections[collectionIndex].refresh();
            }

            for (collectionIndex = 0; collectionIndex < this._timeSlotCollections.length; collectionIndex++) {
                this._timeSlotCollections[collectionIndex].refresh();
            }
        },

        timeSlotRanges: function (startTime, endTime) {
            var collections = this._timeSlotCollections;

            var start = this._startSlot(startTime, collections);

            if (!start.inRange && startTime >= start.slot.end) {
                start = null;
            }

            var end = start;

            if (startTime < endTime) {
                end = this._endSlot(endTime, collections);
            }

            if (end && !end.inRange && endTime <= end.slot.start) {
                end = null;
            }

            if (start === null && end === null) {
                return [];
            }

            if (start === null) {
                start = {
                    inRange: true,
                    slot: collections[end.slot.collectionIndex].first()
                };
            }

            if (end === null) {
                end = {
                    inRange: true,
                    slot: collections[start.slot.collectionIndex].last()
                };
            }

            return this._continuousRange(TimeSlotRange, collections, start, end);
        },

        daySlotRanges: function (startTime, endTime, isAllDay) {
            var collections = this._daySlotCollections;

            var start = this._startSlot(startTime, collections, isAllDay);

            if (!start.inRange && startTime >= start.slot.end) {
                start = null;
            }

            var end = start;

            if (startTime < endTime) {
                end = this._endSlot(endTime, collections, isAllDay);
            }

            if (end && !end.inRange && endTime <= end.slot.start) {
                end = null;
            }

            if (start === null && end === null) {
                return [];
            }

            if (start === null) {
                do {
                    startTime += kendo.date.MS_PER_DAY;
                    start = this._startSlot(startTime, collections, isAllDay);
                } while (!start.inRange && startTime >= start.slot.end);
            }

            if (end === null) {
                do {
                    endTime -= kendo.date.MS_PER_DAY;
                    end = this._endSlot(endTime, collections, isAllDay);
                } while (!end.inRange && endTime <= end.slot.start);
            }

            return this._continuousRange(DaySlotRange, collections, start, end);
        },

        _continuousRange: function (range, collections, start, end) {
            var startSlot = start.slot;
            var endSlot = end.slot;

            var startIndex = startSlot.collectionIndex;
            var endIndex = endSlot.collectionIndex;

            var ranges = [];

            for (var collectionIndex = startIndex; collectionIndex <= endIndex; collectionIndex++) {
                var collection = collections[collectionIndex];

                var first = collection.first();
                var last = collection.last();
                var head = false;
                var tail = false;

                if (collectionIndex == startIndex) {
                    tail = !start.inRange;
                }

                if (collectionIndex == endIndex) {
                    head = !end.inRange;
                }

                if (first.start < startSlot.start) {
                    first = startSlot;
                }

                if (last.start > endSlot.start) {
                    last = endSlot;
                }

                if (startIndex < endIndex) {
                    if (collectionIndex == startIndex) {
                        head = true;
                    } else if (collectionIndex == endIndex) {
                        tail = true;
                    } else {
                        head = tail = true;
                    }
                }

                ranges.push(new range({
                    start: first,
                    end: last,
                    collection: collection,
                    head: head,
                    tail: tail
                }));
            }

            return ranges;
        },

        slotRanges: function (event, isDay) {
            var startTime = event.startTime || kendo.date.toUtcTime(event.start);
            var endTime = event.endTime || kendo.date.toUtcTime(event.end);

            if (isDay === undefined) {
                isDay = event.isMultiDay();
            }

            if (isDay) {
                return this.daySlotRanges(startTime, endTime, event.isAllDay);
            }

            return this.timeSlotRanges(startTime, endTime);
        },

        ranges: function (startTime, endTime, isDay, isAllDay) {
            if (typeof startTime != "number") {
                startTime = kendo.date.toUtcTime(startTime);
            }

            if (typeof endTime != "number") {
                endTime = kendo.date.toUtcTime(endTime);
            }

            if (isDay) {
                return this.daySlotRanges(startTime, endTime, isAllDay);
            }

            return this.timeSlotRanges(startTime, endTime);
        },

        _startCollection: function (date, collections) {
            for (var collectionIndex = 0; collectionIndex < collections.length; collectionIndex++) {
                var collection = collections[collectionIndex];

                if (collection.startInRange(date)) {
                    return collection;
                }
            }

            return null;
        },

        _endCollection: function (date, collections, isAllDay) {
            for (var collectionIndex = 0; collectionIndex < collections.length; collectionIndex++) {
                var collection = collections[collectionIndex];

                if (collection.endInRange(date, isAllDay)) {
                    return collection;
                }
            }

            return null;
        },

        _getCollections: function (isDay) {
            return isDay ? this._daySlotCollections : this._timeSlotCollections;
        },

        continuousSlot: function (slot, reverse) {
            var pad = reverse ? -1 : 1;
            var collections = this._getCollections(slot.isDaySlot);
            var collection = collections[slot.collectionIndex + pad];

            return collection ? collection[reverse ? "last" : "first"]() : undefined;
        },

        firstSlot: function () {
            var collections = this._getCollections(this.daySlotCollectionCount());

            return collections[0].first();
        },

        lastSlot: function () {
            var collections = this._getCollections(this.daySlotCollectionCount());

            return collections[collections.length - 1].last();
        },

        upSlot: function (slot, keepCollection) {
            var that = this;
            var moveToDaySlot = function (isDaySlot, collectionIndex, index) {
                var isFirstCell = index === 0;

                if (!keepCollection && !isDaySlot && isFirstCell && that.daySlotCollectionCount()) {
                    return that._daySlotCollections[0].at(collectionIndex);
                }
            };

            if (!this.timeSlotCollectionCount()) {
                keepCollection = true;
            }

            return this._verticalSlot(slot, -1, moveToDaySlot);
        },

        downSlot: function (slot, keepCollection) {
            var that = this;
            var moveToTimeSlot = function (isDaySlot, collectionIndex, index) {
                if (!keepCollection && isDaySlot && that.timeSlotCollectionCount()) {
                    return that._timeSlotCollections[index].at(0);
                }
            };

            if (!this.timeSlotCollectionCount()) {
                keepCollection = true;
            }

            return this._verticalSlot(slot, 1, moveToTimeSlot);
        },

        leftSlot: function (slot) {
            return this._horizontalSlot(slot, -1);
        },

        rightSlot: function (slot) {
            return this._horizontalSlot(slot, 1);
        },

        _horizontalSlot: function (slot, step) {
            var index = slot.index;
            var isDaySlot = slot.isDaySlot;
            var collectionIndex = slot.collectionIndex;
            var collections = this._getCollections(isDaySlot);

            if (isDaySlot) {
                index += step;
            } else {
                collectionIndex += step;
            }

            var collection = collections[collectionIndex];

            return collection ? collection.at(index) : undefined;
        },

        _verticalSlot: function (slot, step, swapCollection) {
            var index = slot.index;
            var isDaySlot = slot.isDaySlot;
            var collectionIndex = slot.collectionIndex;
            var collections = this._getCollections(isDaySlot);

            slot = swapCollection(isDaySlot, collectionIndex, index);
            if (slot) {
                return slot;
            }

            if (isDaySlot) {
                collectionIndex += step;
            } else {
                index += step;
            }

            var collection = collections[collectionIndex];

            return collection ? collection.at(index) : undefined;
        },

        _collection: function (index, multiday) {
            var collections = multiday ? this._daySlotCollections : this._timeSlotCollections;

            return collections[index];
        },

        _startSlot: function (time, collections, isAllDay) {
            var collection = this._startCollection(time, collections);

            var inRange = true;

            if (!collection) {
                collection = collections[0];
                inRange = false;
            }

            var slot = collection.slotByStartDate(time, isAllDay);

            if (!slot) {
                slot = collection.first();
                inRange = false;
            }

            return {
                slot: slot,
                inRange: inRange
            };
        },

        _endSlot: function (time, collections, isAllDay) {
            var collection = this._endCollection(time, collections, isAllDay);

            var inRange = true;

            if (!collection) {
                collection = collections[collections.length - 1];
                inRange = false;
            }

            var slot = collection.slotByEndDate(time, isAllDay);

            if (!slot) {
                slot = collection.last();
                inRange = false;
            }

            return {
                slot: slot,
                inRange: inRange
            };
        },

        getSlotCollection: function (index, isDay) {
            return this[isDay ? "getDaySlotCollection" : "getTimeSlotCollection"](index);
        },

        getTimeSlotCollection: function (index) {
            return this._timeSlotCollections[index];
        },

        getDaySlotCollection: function (index) {
            return this._daySlotCollections[index];
        }
    });

    var SlotRange = kendo.Class.extend({
        init: function (options) {
            $.extend(this, options);
        },

        innerHeight: function () {
            var collection = this.collection;

            var startIndex = this.start.index;

            var endIndex = this.end.index;

            var result = 0;

            for (var slotIndex = startIndex; slotIndex <= endIndex; slotIndex++) {
                result += collection.at(slotIndex).offsetHeight;
            }

            return result;
        },

        events: function () {
            return this.collection.events();
        },

        addEvent: function (event) {
            this.events().push(event);
        },

        startSlot: function () {
            if (this.start.offsetLeft > this.end.offsetLeft) {
                return this.end;
            }
            return this.start;
        },

        endSlot: function () {
            if (this.start.offsetLeft > this.end.offsetLeft) {
                return this.start;
            }
            return this.end;
        }
    });

    var TimeSlotRange = SlotRange.extend({
        innerHeight: function () {
            var collection = this.collection;

            var startIndex = this.start.index;

            var endIndex = this.end.index;

            var result = 0;

            for (var slotIndex = startIndex; slotIndex <= endIndex; slotIndex++) {
                result += collection.at(slotIndex).offsetHeight;
            }

            return result;
        },

        outerRect: function (start, end, snap) {
            return this._rect("offset", start, end, snap);
        },

        _rect: function (property, start, end, snap) {
            var top;
            var bottom;
            var left;
            var right;
            var startSlot = this.start;
            var endSlot = this.end;
            var isRtl = kendo.support.isRtl(startSlot.element);

            if (typeof start != "number") {
                start = kendo.date.toUtcTime(start);
            }

            if (typeof end != "number") {
                end = kendo.date.toUtcTime(end);
            }

            if (snap) {
                top = startSlot.offsetTop;
                bottom = endSlot.offsetTop + endSlot[property + "Height"];
                if (isRtl) {
                    left = endSlot.offsetLeft;
                    right = startSlot.offsetLeft + startSlot[property + "Width"];
                } else {
                    left = startSlot.offsetLeft;
                    right = endSlot.offsetLeft + endSlot[property + "Width"];
                }
            } else {
                var startOffset = start - startSlot.start;

                if (startOffset < 0) {
                    startOffset = 0;
                }

                var startSlotDuration = startSlot.end - startSlot.start;

                top = startSlot.offsetTop + startSlot[property + "Height"] * startOffset / startSlotDuration;

                var endOffset = endSlot.end - end;

                if (endOffset < 0) {
                    endOffset = 0;
                }

                var endSlotDuration = endSlot.end - endSlot.start;

                bottom = endSlot.offsetTop + endSlot[property + "Height"] - endSlot[property + "Height"] * endOffset / endSlotDuration;

                if (isRtl) {
                    left = Math.round(endSlot.offsetLeft + endSlot[property + "Width"] * endOffset / endSlotDuration);
                    right = Math.round(startSlot.offsetLeft + startSlot[property + "Width"] - startSlot[property + "Width"] * startOffset / startSlotDuration);
                } else {
                    left = Math.round(startSlot.offsetLeft + startSlot[property + "Width"] * startOffset / startSlotDuration);
                    right = Math.round(endSlot.offsetLeft + endSlot[property + "Width"] - endSlot[property + "Width"] * endOffset / endSlotDuration);
                }
            }

            return {
                top: top,
                bottom: bottom,
                //first column has no left border
                left: left === 0 ? left : left + 1,
                right: right
            };
        },

        innerRect: function (start, end, snap) {
            return this._rect("client", start, end, snap);
        }
    });

    var DaySlotRange = SlotRange.extend({
        innerWidth: function () {
            var collection = this.collection;

            var startIndex = this.start.index;

            var endIndex = this.end.index;

            var result = 0;

            var width = startIndex !== endIndex ? "offsetWidth" : "clientWidth";

            for (var slotIndex = startIndex; slotIndex <= endIndex; slotIndex++) {
                result += collection.at(slotIndex)[width];
            }

            return result;
        }
    });

    var SlotCollection = kendo.Class.extend({
        init: function (startDate, endDate, groupIndex, collectionIndex) {
            this._slots = [];

            this._events = [];

            this._start = kendo.date.toUtcTime(startDate);

            this._end = kendo.date.toUtcTime(endDate);

            this._groupIndex = groupIndex;

            this._collectionIndex = collectionIndex;
        },
        refresh: function () {
            for (var slotIndex = 0; slotIndex < this._slots.length; slotIndex++) {
                this._slots[slotIndex].refresh();
            }
        },

        startInRange: function (date) {
            return this._start <= date && date < this._end;
        },

        endInRange: function (date, isAllDay) {
            var end = isAllDay ? date < this._end : date <= this._end;
            return this._start <= date && end;
        },

        slotByStartDate: function (date) {
            var time = date;

            if (typeof time != "number") {
                time = kendo.date.toUtcTime(date);
            }

            for (var slotIndex = 0; slotIndex < this._slots.length; slotIndex++) {
                var slot = this._slots[slotIndex];

                if (slot.startInRange(time)) {
                    return slot;
                }
            }

            return null;
        },

        slotByEndDate: function (date, allday) {
            var time = date;

            if (typeof time != "number") {
                time = kendo.date.toUtcTime(date);
            }

            if (allday) {
                return this.slotByStartDate(date, false);
            }

            for (var slotIndex = 0; slotIndex < this._slots.length; slotIndex++) {
                var slot = this._slots[slotIndex];

                if (slot.endInRange(time)) {
                    return slot;
                }
            }

            return null;
        },

        count: function () {
            return this._slots.length;
        },
        events: function () {
            return this._events;
        },
        addTimeSlot: function (element, start, end, isHorizontal) {
            var slot = new TimeSlot(element, start, end, this._groupIndex, this._collectionIndex, this._slots.length, isHorizontal);

            this._slots.push(slot);
        },
        addDaySlot: function (element, start, end, eventCount) {
            var slot = new DaySlot(element, start, end, this._groupIndex, this._collectionIndex, this._slots.length, eventCount);

            this._slots.push(slot);
        },
        first: function () {
            return this._slots[0];
        },
        last: function () {
            return this._slots[this._slots.length - 1];
        },
        at: function (index) {
            return this._slots[index];
        }
    });

    var Slot = kendo.Class.extend({
        init: function (element, start, end, groupIndex, collectionIndex, index) {
            this.element = element;
            this.clientWidth = element.clientWidth;
            this.clientHeight = element.clientHeight;
            this.offsetWidth = element.offsetWidth;
            this.offsetHeight = element.offsetHeight;
            this.offsetTop = element.offsetTop;
            this.offsetLeft = element.offsetLeft;
            this.start = start;
            this.end = end;
            this.element = element;
            this.groupIndex = groupIndex;
            this.collectionIndex = collectionIndex;
            this.index = index;
            this.isDaySlot = false;
        },

        startDate: function () {
            return kendo.timezone.toLocalDate(this.start);
        },

        endDate: function () {
            return kendo.timezone.toLocalDate(this.end);
        },

        startInRange: function (date) {
            return this.start <= date && date < this.end;
        },

        endInRange: function (date) {
            return this.start < date && date <= this.end;
        },

        startOffset: function () {
            return this.start;
        },

        endOffset: function () {
            return this.end;
        }
    });

    var TimeSlot = Slot.extend({
        init: function (element, start, end, groupIndex, collectionIndex, index, isHorizontal) {
            Slot.fn.init.apply(this, arguments);

            this.isHorizontal = isHorizontal ? true : false;
        },

        refresh: function () {
            var element = this.element;

            this.clientWidth = element.clientWidth;
            this.clientHeight = element.clientHeight;
            this.offsetWidth = element.offsetWidth;
            this.offsetHeight = element.offsetHeight;
            this.offsetTop = element.offsetTop;
            this.offsetLeft = element.offsetLeft;
        },

        offsetX: function (rtl, offset) {
            if (rtl) {
                return this.offsetLeft + offset;
            } else {
                return this.offsetLeft + offset;
            }
        },

        startInRange: function (date) {
            return this.start <= date && date < this.end;
        },

        endInRange: function (date) {
            return this.start < date && date <= this.end;
        },

        startOffset: function (x, y, snap) {
            if (snap) {
                return this.start;
            }

            var offset = $(this.element).offset();

            var duration = this.end - this.start;
            var difference;
            var time;

            if (this.isHorizontal) {
                //need update
                var isRtl = kendo.support.isRtl(this.element);
                difference = x - offset.left;
                time = Math.floor(duration * (difference / this.offsetWidth));

                if (isRtl) {
                    return this.start + duration - time;
                }
            } else {
                difference = y - offset.top;
                time = Math.floor(duration * (difference / this.offsetHeight));
            }

            return this.start + time;
        },

        endOffset: function (x, y, snap) {
            if (snap) {
                return this.end;
            }

            var offset = $(this.element).offset();

            var duration = this.end - this.start;
            var difference;
            var time;

            if (this.isHorizontal) {
                //need update
                var isRtl = kendo.support.isRtl(this.element);
                difference = x - offset.left;
                time = Math.floor(duration * (difference / this.offsetWidth));

                if (isRtl) {
                    return this.start + duration - time;
                }
            } else {
                difference = y - offset.top;
                time = Math.floor(duration * (difference / this.offsetHeight));
            }

            return this.start + time;
        }
    });

    var DaySlot = Slot.extend({
        init: function (element, start, end, groupIndex, collectionIndex, index, eventCount) {
            Slot.fn.init.apply(this, arguments);

            this.eventCount = eventCount;
            this.isDaySlot = true;

            if (this.element.children.length) {
                this.firstChildHeight = this.element.children[0].offsetHeight + 3;
                this.firstChildTop = this.element.children[0].offsetTop;
            } else {
                this.firstChildHeight = 3;
                this.firstChildTop = 0;
            }
        },

        refresh: function () {
            this.clientHeight = this.element.clientHeight;
            this.offsetTop = this.element.offsetTop;
        },

        startDate: function () {
            var date = new Date(this.start);

            return kendo.timezone.apply(date, "Etc/UTC");
        },

        endDate: function () {
            var date = new Date(this.end);

            return kendo.timezone.apply(date, "Etc/UTC");
        },

        startInRange: function (date) {
            return this.start <= date && date < this.end;
        },

        endInRange: function (date) {
            return this.start < date && date <= this.end;
        }
    });

    var scrollbarWidth;
    function scrollbar() {
        scrollbarWidth = scrollbarWidth ? scrollbarWidth : kendo.support.scrollbar();
        return scrollbarWidth;
    }

    kendo.ui.SchedulerView = Widget.extend({
        init: function (element, options) {
            Widget.fn.init.call(this, element, options);

            this._scrollbar = scrollbar();
            this._isRtl = kendo.support.isRtl(element);
            this._resizeHint = $();
            this._moveHint = $();
            this._cellId = kendo.guid();
            this._resourcesForGroups();
            this._selectedSlots = [];
        },

        _isMobile: function () {
            var options = this.options;
            return (options.mobile === true && kendo.support.mobileOS) || options.mobile === "phone" || options.mobile === "tablet";
        },

        _isMobilePhoneView: function () {
            var options = this.options;
            return (options.mobile === true && kendo.support.mobileOS && !kendo.support.mobileOS.tablet) || options.mobile === "phone";
        },

        _addResourceView: function () {
            var resourceView = new ResourceView(this.groups.length);

            this.groups.push(resourceView);

            return resourceView;
        },

        dateForTitle: function () {
            return kendo.format(this.options.selectedDateFormat, this.startDate(), this.endDate());
        },

        _changeGroup: function (selection, previous) {
            var method = previous ? "prevGroupSlot" : "nextGroupSlot";
            var slot = this[method](selection.start, selection.groupIndex, selection.isAllDay);

            if (slot) {
                selection.groupIndex += previous ? -1 : 1;
            }

            return slot;
        },

        _changeGroupContinuously: function () {
            return null;
        },

        _changeViewPeriod: function () {
            return false;
        },

        _horizontalSlots: function (selection, ranges, multiple, reverse) {
            var method = reverse ? "leftSlot" : "rightSlot";
            var startSlot = ranges[0].start;
            var endSlot = ranges[ranges.length - 1].end;
            var group = this.groups[selection.groupIndex];

            if (!multiple) {
                var slot = this._normalizeHorizontalSelection(selection, ranges, reverse);
                if (slot) {
                    startSlot = endSlot = slot;
                }
            }

            startSlot = group[method](startSlot);
            endSlot = group[method](endSlot);

            if (!multiple && !this._isVerticallyGrouped() && (!startSlot || !endSlot)) {
                startSlot = endSlot = this._changeGroup(selection, reverse);
            }

            var continuousSlot;

            if (!startSlot || !endSlot) {
                continuousSlot = this._continuousSlot(selection, ranges, reverse);
                continuousSlot = this._changeGroupContinuously(selection, continuousSlot, multiple, reverse);

                if (continuousSlot) {
                    startSlot = endSlot = continuousSlot;
                }
            }

            return {
                startSlot: startSlot,
                endSlot: endSlot
            };
        },

        _verticalSlots: function (selection, ranges, multiple, reverse) {
            var startSlot = ranges[0].start;
            var endSlot = ranges[ranges.length - 1].end;
            var group = this.groups[selection.groupIndex];

            if (!multiple) {
                var slot = this._normalizeVerticalSelection(selection, ranges, reverse);
                if (slot) {
                    startSlot = endSlot = slot;
                }
            }

            var method = reverse ? "upSlot" : "downSlot";

            startSlot = group[method](startSlot, multiple);
            endSlot = group[method](endSlot, multiple);

            if (!multiple && this._isVerticallyGrouped() && (!startSlot || !endSlot)) {
                startSlot = endSlot = this._changeGroup(selection, reverse);
            }

            return {
                startSlot: startSlot,
                endSlot: endSlot
            };
        },

        _normalizeHorizontalSelection: function () {
            return null;
        },

        _normalizeVerticalSelection: function (selection, ranges, reverse) {
            var slot;

            if (reverse) {
                slot = ranges[0].start;
            } else {
                slot = ranges[ranges.length - 1].end;
            }

            return slot;
        },

        _continuousSlot: function () {
            return null;
        },

        constrainSelection: function (selection) {
            var group = this.groups[0];
            var slot;

            if (!this.inRange(selection)) {
                slot = group.firstSlot();

                selection.isAllDay = slot.isDaySlot;
                selection.start = slot.startDate();
                selection.end = slot.endDate();
            } else {
                if (!group.daySlotCollectionCount()) {
                    selection.isAllDay = false;
                }
            }

            if (!this.groups[selection.groupIndex]) {
                selection.groupIndex = 0;
            }
        },

        move: function (selection, key, shift) {
            var handled = false;
            var group = this.groups[selection.groupIndex];

            if (!group.timeSlotCollectionCount()) {
                selection.isAllDay = true;
            }

            var ranges = group.ranges(selection.start, selection.end, selection.isAllDay, false);
            var startSlot, endSlot, reverse, slots;

            if (key === keys.DOWN || key === keys.UP) {
                handled = true;
                reverse = key === keys.UP;

                this._updateDirection(selection, ranges, shift, reverse, true);

                slots = this._verticalSlots(selection, ranges, shift, reverse);

                if (!slots.startSlot && !shift && this._changeViewPeriod(selection, reverse, true)) {
                    return handled;
                }

            } else if (key === keys.LEFT || key === keys.RIGHT) {
                handled = true;
                reverse = key === keys.LEFT;

                this._updateDirection(selection, ranges, shift, reverse, false);

                slots = this._horizontalSlots(selection, ranges, shift, reverse);

                if (!slots.startSlot && !shift && this._changeViewPeriod(selection, reverse, false)) {
                    return handled;
                }
            }

            if (handled) {
                startSlot = slots.startSlot;
                endSlot = slots.endSlot;

                if (shift) {
                    var backward = selection.backward;
                    if (backward && startSlot) {
                        selection.start = startSlot.startDate();
                    } else if (!backward && endSlot) {
                        selection.end = endSlot.endDate();
                    }
                } else if (startSlot && endSlot) {
                    selection.isAllDay = startSlot.isDaySlot;
                    selection.start = startSlot.startDate();
                    selection.end = endSlot.endDate();
                }

                selection.events = [];
            }

            return handled;
        },

        moveToEventInGroup: function (group, slot, selectedEvents, prev) {
            var events = group._continuousEvents || [];

            var found, event;

            var pad = prev ? -1 : 1;

            var length = events.length;
            var idx = prev ? length - 1 : 0;

            while (idx < length && idx > -1) {
                event = events[idx];

                if ((!prev && event.start.startDate() >= slot.startDate()) ||
                    (prev && event.start.startDate() <= slot.startDate())) {

                    if (selectedEvents.length) {
                        event = events[idx + pad];
                    }

                    if (event && $.inArray(event.uid, selectedEvents) === -1) {
                        found = !!event;
                        break;
                    }
                }

                idx += pad;
            }

            return event;
        },

        moveToEvent: function (selection, prev) {
            var groupIndex = selection.groupIndex;

            var group = this.groups[groupIndex];
            var slot = group.ranges(selection.start, selection.end, selection.isAllDay, false)[0].start;

            var length = this.groups.length;
            var pad = prev ? -1 : 1;
            var events = selection.events;
            var event;

            while (groupIndex < length && groupIndex > -1) {
                event = this.moveToEventInGroup(group, slot, events, prev);

                groupIndex += pad;
                group = this.groups[groupIndex];

                if (!group || event) {
                    break;
                }

                events = [];
                if (prev) {
                    slot = group.lastSlot();
                } else {
                    slot = group.firstSlot(true);
                }
            }

            if (event) {
                selection.events = [event.uid];
                selection.start = event.start.startDate();
                selection.end = event.end.endDate();
                selection.isAllDay = event.start.isDaySlot;
                selection.groupIndex = event.start.groupIndex;
            }

            return !!event;
        },

        current: function (candidate) {
            if (candidate !== undefined) {
                this._current = candidate;
                this._scrollTo(candidate, this.content[0]);
            } else {
                return this._current;
            }
        },

        select: function (selection) {
            this.clearSelection();

            if (!this._selectEvents(selection)) {
                this._selectSlots(selection);
            }
        },

        _selectSlots: function (selection) {
            var isAllDay = selection.isAllDay;
            var group = this.groups[selection.groupIndex];

            if (!group.timeSlotCollectionCount()) {
                isAllDay = true;
            }

            this._selectedSlots = [];

            var ranges = group.ranges(selection.start, selection.end, isAllDay, false);
            var element;
            var slot;

            for (var rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) {
                var range = ranges[rangeIndex];
                var collection = range.collection;

                for (var slotIndex = range.start.index; slotIndex <= range.end.index; slotIndex++) {
                    slot = collection.at(slotIndex);

                    element = slot.element;
                    element.setAttribute("aria-selected", true);
                    addSelectedState(element);

                    this._selectedSlots.push({
                        start: slot.startDate(),
                        end: slot.endDate(),
                        element: element
                    });
                }
            }

            if (selection.backward) {
                element = ranges[0].start.element;
            }

            this.current(element);
        },

        _selectEvents: function (selection) {
            var found = false;
            var events = selection.events;
            var groupEvents = this.groups[selection.groupIndex]._continuousEvents || [];
            var idx, length = groupEvents.length;

            if (!events[0] || !groupEvents[0]) {
                return found;
            }

            var result = $();
            selection.events = [];
            for (idx = 0; idx < length; idx++) {
                if ($.inArray(groupEvents[idx].uid, events) > -1) {
                    result = result.add(groupEvents[idx].element);
                    selection.events.push(groupEvents[idx].uid);
                }
            }

            if (result[0]) {
                result.addClass("k-state-selected").attr("aria-selected", true);
                this.current(result.last()[0]);
                this._selectedSlots = [];
                found = true;
            }

            return found;
        },

        inRange: function (options) {
            var startDate = this.startDate();
            var endDate = kendo.date.addDays(this.endDate(), 1);
            var start = options.start;
            var end = options.end;

            return startDate <= start && start < endDate && startDate < end && end <= endDate;
        },

        _resourceValue: function (resource, item) {
            if (resource.valuePrimitive) {
                item = kendo.getter(resource.dataValueField)(item);
            }
            return item;
        },

        _resourceBySlot: function (slot) {
            var resources = this.groupedResources;
            var result = {};

            if (resources.length) {
                var resourceIndex = slot.groupIndex;

                for (var idx = resources.length - 1; idx >= 0; idx--) {
                    var resource = resources[idx];

                    var value = this._resourceValue(resource, resource.dataSource.view()[resourceIndex % resource.dataSource.total()]);

                    if (resource.multiple) {
                        value = [value];
                    }

                    var setter = kendo.setter(resource.field);
                    setter(result, value);

                    resourceIndex = Math.floor(resourceIndex / resource.dataSource.total());
                }
            }

            return result;
        },

        _createResizeHint: function (left, top, width, height) {
            return $(HINT).css({
                left: left,
                top: top,
                width: width,
                height: height
            });
        },

        _removeResizeHint: function () {
            this._resizeHint.remove();
            this._resizeHint = $();
        },

        _removeMoveHint: function () {
            this._moveHint.remove();
            this._moveHint = $();
        },

        _scrollTo: function (element, container) {
            var elementOffset = element.offsetTop,
                elementOffsetDir = element.offsetHeight,
                containerScroll = container.scrollTop,
                containerOffsetDir = container.clientHeight,
                bottomDistance = elementOffset + elementOffsetDir,
                result = 0;

            if (containerScroll > elementOffset) {
                result = elementOffset;
            } else if (bottomDistance > (containerScroll + containerOffsetDir)) {
                if (elementOffsetDir <= containerOffsetDir) {
                    result = (bottomDistance - containerOffsetDir);
                } else {
                    result = elementOffset;
                }
            } else {
                result = containerScroll;
            }
            container.scrollTop = result;
        },

        _shouldInverseResourceColor: function (resource) {
            var resourceColorIsDark = new Color(resource.color).isDark();
            var currentColor = this.element.css("color");
            var currentColorIsDark = new Color(currentColor).isDark();

            return (resourceColorIsDark == currentColorIsDark);
        },

        _eventTmpl: function (template, wrapper) {
            var options = this.options,
               settings = $.extend({}, kendo.Template, options.templateSettings),
               paramName = settings.paramName,
               html = "",
               type = typeof template,
               state = { storage: {}, count: 0 };

            if (type === "function") {
                state.storage["tmpl" + state.count] = template;
                html += "#=this.tmpl" + state.count + "(" + paramName + ")#";
                state.count++;
            } else if (type === "string") {
                html += template;
            }

            var tmpl = kendo.template(kendo.format(wrapper, html), settings);

            if (state.count > 0) {
                tmpl = $.proxy(tmpl, state.storage);
            }

            return tmpl;
        },

        eventResources: function (event) {
            var resources = [],
                options = this.options;

            if (!options.resources) {
                return resources;
            }

            for (var idx = 0; idx < options.resources.length; idx++) {
                var resource = options.resources[idx];
                var field = resource.field;
                var eventResources = kendo.getter(field)(event);

                if (!eventResources) {
                    continue;
                }

                if (!resource.multiple) {
                    eventResources = [eventResources];
                }

                var data = resource.dataSource.view();

                for (var resourceIndex = 0; resourceIndex < eventResources.length; resourceIndex++) {
                    var eventResource = null;

                    var value = eventResources[resourceIndex];

                    if (!resource.valuePrimitive) {
                        value = kendo.getter(resource.dataValueField)(value);
                    }

                    for (var dataIndex = 0; dataIndex < data.length; dataIndex++) {
                        if (data[dataIndex].get(resource.dataValueField) == value) {
                            eventResource = data[dataIndex];
                            break;
                        }
                    }

                    if (eventResource !== null) {
                        var resourceColor = kendo.getter(resource.dataColorField)(eventResource);
                        resources.push({
                            field: resource.field,
                            title: resource.title,
                            name: resource.name,
                            text: kendo.getter(resource.dataTextField)(eventResource),
                            value: value,
                            color: resourceColor
                        });
                    }
                }
            }
            return resources;
        },

        createLayout: function (layout) {
            var allDayIndex = -1;

            if (!layout.rows) {
                layout.rows = [];
            }

            for (var idx = 0; idx < layout.rows.length; idx++) {
                if (layout.rows[idx].allDay) {
                    allDayIndex = idx;
                    break;
                }
            }

            var allDaySlot = layout.rows[allDayIndex];

            if (allDayIndex >= 0) {
                layout.rows.splice(allDayIndex, 1);
            }

            var columnLevels = this.columnLevels = levels(layout, "columns");

            var rowLevels = this.rowLevels = levels(layout, "rows");

            this.table = $('<table ' + cellspacing() + ' class="k-scheduler-layout k-scheduler-' + this.name + 'view"/>');

            var rowCount = rowLevels[rowLevels.length - 1].length;

            this.table.append(this._topSection(columnLevels, allDaySlot, rowCount));

            this.table.append(this._bottomSection(columnLevels, rowLevels, rowCount));

            this.element.append(this.table);

            this._scroller();
        },

        refreshLayout: function () {
            var that = this,
                toolbar = that.element.find(">.k-scheduler-toolbar"),
                height = that.element.innerHeight(),
                scrollbar = this._scrollbar,
                headerHeight = 0,
                paddingDirection = this._isRtl ? "left" : "right";

            for (var idx = 0; idx < toolbar.length; idx++) {
                height -= toolbar.eq(idx).outerHeight();
            }

            if (that.datesHeader) {
                headerHeight = that.datesHeader.outerHeight();
            }

            if (that.timesHeader && that.timesHeader.outerHeight() > headerHeight) {
                headerHeight = that.timesHeader.outerHeight();
            }

            if (that.datesHeader && that.timesHeader) {
                var datesHeaderRows = that.datesHeader.find("table:first tr");

                that.timesHeader.find("tr").height(function (index) {
                    $(this).height(datesHeaderRows.eq(index).height());
                });
            }

            if (headerHeight) {
                height -= headerHeight;
            }

            if (that.footer) {
                height -= that.footer.outerHeight();
            }

            var isSchedulerHeightSet = function (el) {
                var initialHeight, newHeight;
                if (el[0].style.height) {
                    return true;
                } else {
                    initialHeight = el.height();
                }

                el.height("auto");
                newHeight = el.height();

                if (initialHeight != newHeight) {
                    el.height("");
                    return true;
                }
                el.height("");
                return false;
            };

            var contentDiv = that.content[0],
                scrollbarWidth = !kendo.support.kineticScrollNeeded ? scrollbar : 0;

            if (isSchedulerHeightSet(that.element)) { // set content height only if needed
                if (height > scrollbar * 2) { // do not set height if proper scrollbar cannot be displayed
                    that.content.height(height);
                } else {
                    that.content.height(scrollbar * 2 + 1);
                }
                that.times.height(contentDiv.clientHeight);

                var timesTable = that.times.find("table");
                if (timesTable.length) {
                    timesTable.height(that.content.find("table")[0].clientHeight);
                }
            }


            if (contentDiv.offsetWidth - contentDiv.clientWidth > 0) {
                that.table.addClass("k-scrollbar-v");
                that.datesHeader.css("padding-" + paddingDirection, scrollbarWidth - parseInt(that.datesHeader.children().css("border-" + paddingDirection + "-width"), 10));
            } else {
                that.datesHeader.css("padding-" + paddingDirection, "");
            }
            if (contentDiv.offsetHeight - contentDiv.clientHeight > 0 || contentDiv.clientHeight > that.content.children(".k-scheduler-table").height()) {
                that.table.addClass("k-scrollbar-h");
            } else {
                that.table.removeClass("k-scrollbar-h");
            }
        },

        _topSection: function (columnLevels, allDaySlot, rowCount) {
            this.timesHeader = timesHeader(columnLevels.length, allDaySlot, rowCount);

            var columnCount = columnLevels[columnLevels.length - 1].length;

            this.datesHeader = datesHeader(columnLevels, columnCount, allDaySlot);

            return $("<tr>").append(this.timesHeader.add(this.datesHeader).wrap("<td>").parent());
        },

        _bottomSection: function (columnLevels, rowLevels, rowCount) {
            this.times = times(rowLevels, rowCount);

            this.content = content(columnLevels[columnLevels.length - 1], rowLevels[rowLevels.length - 1]);

            return $("<tr>").append(this.times.add(this.content).wrap("<td>").parent());
        },

        _scroller: function () {
            var that = this;

            this.content.bind("scroll" + NS, function () {
                that.datesHeader.find(">.k-scheduler-header-wrap").scrollLeft(this.scrollLeft);
                that.times.scrollTop(this.scrollTop);
            });

            var touchScroller = kendo.touchScroller(this.content, {
                avoidScrolling: function (e) {
                    return $(e.event.target).closest(".k-event.k-event-active").length > 0;
                }
            });

            if (touchScroller && touchScroller.movable) {

                this._touchScroller = touchScroller;

                this.content = touchScroller.scrollElement;

                touchScroller.movable.bind("change", function (e) {
                    that.datesHeader.find(">.k-scheduler-header-wrap").scrollLeft(-e.sender.x);
                    that.times.scrollTop(-e.sender.y);
                });
            }
        },

        _resourcesForGroups: function () {
            var result = [];
            var groups = this.options.group;
            var resources = this.options.resources;

            groups = groups && groups.resources ? groups.resources : [];

            if (resources && groups.length) {
                for (var idx = 0, length = resources.length; idx < length; idx++) {
                    for (var groupIdx = 0, groupLength = groups.length; groupIdx < groupLength; groupIdx++) {
                        if (resources[idx].name === groups[groupIdx]) {
                            result.push(resources[idx]);
                        }
                    }
                }
            }

            this.groupedResources = result;
        },

        _createColumnsLayout: function (resources, inner, template) {
            return createLayoutConfiguration("columns", resources, inner, template);
        },

        _groupOrientation: function () {
            var groups = this.options.group;
            return groups && groups.resources ? groups.orientation : "horizontal";
        },

        _isVerticallyGrouped: function () {
            return this.groupedResources.length && this._groupOrientation() === "vertical";
        },

        _createRowsLayout: function (resources, inner, template) {
            return createLayoutConfiguration("rows", resources, inner, template);
        },

        selectionByElement: function () {
            return null;
        },

        clearSelection: function () {
            this.content
                .find(".k-state-selected")
                .removeAttr("id")
                .attr("aria-selected", false)
                .removeClass("k-state-selected");
        },

        destroy: function () {
            var that = this;

            Widget.fn.destroy.call(this);

            if (that.table) {
                kendo.destroy(that.table);
                that.table.remove();
            }

            that.groups = null;
            that.table = null;
            that.content = null;
            that.times = null;
            that.datesHeader = null;
            that.timesHeader = null;
            that.footer = null;
            that._resizeHint = null;
            that._moveHint = null;
        },

        calendarInfo: function () {
            return kendo.getCulture().calendars.standard;
        },

        prevGroupSlot: function (date, groupIndex, isDay) {
            var collection;
            var group = this.groups[groupIndex];
            var slot = group.ranges(date, date, isDay, false)[0].start;

            if (groupIndex <= 0) {
                return;
            }

            if (this._isVerticallyGrouped()) {
                if (!group.timeSlotCollectionCount()) {
                    collection = group._collection(group.daySlotCollectionCount() - 1, true);
                    return collection.at(slot.index);
                } else {
                    collection = group._collection(isDay ? slot.index : slot.collectionIndex, false);
                    return collection.last();
                }
            } else {
                if (!group.timeSlotCollectionCount()) {
                    collection = group._collection(slot.collectionIndex, true);
                    return collection.last();
                } else {
                    collection = group._collection(isDay ? 0 : group.timeSlotCollectionCount() - 1, isDay);
                    return isDay ? collection.last() : collection.at(slot.index);
                }
            }
        },

        nextGroupSlot: function (date, groupIndex, isDay) {
            var collection;
            var group = this.groups[groupIndex];
            var slot = group.ranges(date, date, isDay, false)[0].start;
            var daySlotCollectionCount;

            if (groupIndex >= this.groups.length - 1) {
                return;
            }

            if (this._isVerticallyGrouped()) {
                if (!group.timeSlotCollectionCount()) {
                    collection = group._collection(0, true);
                    return collection.at(slot.index);
                } else {
                    daySlotCollectionCount = group.daySlotCollectionCount();
                    collection = group._collection(daySlotCollectionCount ? 0 : slot.collectionIndex, daySlotCollectionCount);

                    return isDay ? collection.first() : collection.at(slot.collectionIndex);
                }
            } else {
                if (!group.timeSlotCollectionCount()) {
                    collection = group._collection(slot.collectionIndex, true);
                    return collection.first();
                } else {
                    collection = group._collection(0, isDay);
                    return isDay ? collection.first() : collection.at(slot.index);
                }
            }
        },

        _updateEventForMove: function (event) {
            return;
        },

        _updateEventForResize: function (event) {
            return;
        },

        _updateEventForSelection: function (event) {
            return event;
        }
    });

    function collidingEvents(elements, start, end) {
        var idx,
            index,
            startIndex,
            overlaps,
            endIndex;

        for (idx = elements.length - 1; idx >= 0; idx--) {
            index = rangeIndex(elements[idx]);
            startIndex = index.start;
            endIndex = index.end;

            overlaps = startIndex <= start && endIndex >= start;

            if (overlaps || (startIndex >= start && endIndex <= end) || (start <= startIndex && end >= startIndex)) {
                if (startIndex < start) {
                    start = startIndex;
                }

                if (endIndex > end) {
                    end = endIndex;
                }
            }
        }

        return eventsForSlot(elements, start, end);
    }

    function rangeIndex(eventElement) {
        return {
            start: eventElement.start,
            end: eventElement.end
        };
    }

    function eventsForSlot(elements, slotStart, slotEnd) {
        var events = [];

        for (var idx = 0; idx < elements.length; idx++) {
            var event = rangeIndex(elements[idx]);

            if ((event.start < slotStart && event.end > slotStart) || (event.start >= slotStart && event.end <= slotEnd)) {
                events.push(elements[idx]);
            }
        }

        return events;
    }

    function createColumns(eventElements) {
        return _createColumns(eventElements);
    }

    function createRows(eventElements) {
        return _createColumns(eventElements);
    }

    var Color = function (value) {
        var color = this,
            formats = Color.formats,
            re,
            processor,
            parts,
            i,
            channels;

        if (arguments.length === 1) {
            value = color.resolveColor(value);

            for (i = 0; i < formats.length; i++) {
                re = formats[i].re;
                processor = formats[i].process;
                parts = re.exec(value);

                if (parts) {
                    channels = processor(parts);
                    color.r = channels[0];
                    color.g = channels[1];
                    color.b = channels[2];
                }
            }
        } else {
            color.r = arguments[0];
            color.g = arguments[1];
            color.b = arguments[2];
        }

        color.r = color.normalizeByte(color.r);
        color.g = color.normalizeByte(color.g);
        color.b = color.normalizeByte(color.b);
    };

    Color.prototype = {
        resolveColor: function (value) {
            value = value || "#000";

            if (value.charAt(0) == "#") {
                value = value.substr(1, 6);
            }

            value = value.replace(/ /g, "");
            value = value.toLowerCase();
            value = Color.namedColors[value] || value;

            return value;
        },

        normalizeByte: function (value) {
            return (value < 0 || isNaN(value)) ? 0 : ((value > 255) ? 255 : value);
        },

        percBrightness: function () {
            var color = this;
            return math.sqrt(0.241 * color.r * color.r + 0.691 * color.g * color.g + 0.068 * color.b * color.b);
        },

        isDark: function () {
            var color = this;
            var brightnessValue = color.percBrightness();
            return brightnessValue < 180;
        }
    };

    Color.formats = [{
        re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
        process: function (parts) {
            return [
                    parseInt(parts[1], 10), parseInt(parts[2], 10), parseInt(parts[3], 10)
                ];
        }
    }, {
        re: /^(\w{2})(\w{2})(\w{2})$/,
        process: function (parts) {
            return [
                    parseInt(parts[1], 16), parseInt(parts[2], 16), parseInt(parts[3], 16)
                ];
        }
    }, {
        re: /^(\w{1})(\w{1})(\w{1})$/,
        process: function (parts) {
            return [
                    parseInt(parts[1] + parts[1], 16),
                    parseInt(parts[2] + parts[2], 16),
                    parseInt(parts[3] + parts[3], 16)
                ];
        }
    }
    ];

    Color.namedColors = {
        aqua: "00ffff", azure: "f0ffff", beige: "f5f5dc",
        black: "000000", blue: "0000ff", brown: "a52a2a",
        coral: "ff7f50", cyan: "00ffff", darkblue: "00008b",
        darkcyan: "008b8b", darkgray: "a9a9a9", darkgreen: "006400",
        darkorange: "ff8c00", darkred: "8b0000", dimgray: "696969",
        fuchsia: "ff00ff", gold: "ffd700", goldenrod: "daa520",
        gray: "808080", green: "008000", greenyellow: "adff2f",
        indigo: "4b0082", ivory: "fffff0", khaki: "f0e68c",
        lightblue: "add8e6", lightgrey: "d3d3d3", lightgreen: "90ee90",
        lightpink: "ffb6c1", lightyellow: "ffffe0", lime: "00ff00",
        limegreen: "32cd32", linen: "faf0e6", magenta: "ff00ff",
        maroon: "800000", mediumblue: "0000cd", navy: "000080",
        olive: "808000", orange: "ffa500", orangered: "ff4500",
        orchid: "da70d6", pink: "ffc0cb", plum: "dda0dd",
        purple: "800080", red: "ff0000", royalblue: "4169e1",
        salmon: "fa8072", silver: "c0c0c0", skyblue: "87ceeb",
        slateblue: "6a5acd", slategray: "708090", snow: "fffafa",
        steelblue: "4682b4", tan: "d2b48c", teal: "008080",
        tomato: "ff6347", turquoise: "40e0d0", violet: "ee82ee",
        wheat: "f5deb3", white: "ffffff", whitesmoke: "f5f5f5",
        yellow: "ffff00", yellowgreen: "9acd32"
    };

    function _createColumns(eventElements) {
        var columns = [];

        for (var idx = 0; idx < eventElements.length; idx++) {
            var event = eventElements[idx];
            var eventRange = rangeIndex(event);
            var column = null;

            for (var j = 0, columnLength = columns.length; j < columnLength; j++) {
                var endOverlaps = eventRange.start > columns[j].end;

                if (eventRange.start < columns[j].start || endOverlaps) {

                    column = columns[j];

                    if (column.end < eventRange.end) {
                        column.end = eventRange.end;
                    }

                    break;
                }
            }

            if (!column) {
                column = { start: eventRange.start, end: eventRange.end, events: [] };
                columns.push(column);
            }

            column.events.push(event);
        }

        return columns;
    }

    function createLayoutConfiguration(name, resources, inner, template) {
        var resource = resources[0];
        if (resource) {
            var configuration = [];

            var data = resource.dataSource.view();

            for (var dataIndex = 0; dataIndex < data.length; dataIndex++) {
                var obj = {
                    text: template({
                        text: kendo.htmlEncode(kendo.getter(resource.dataTextField)(data[dataIndex])),
                        color: kendo.getter(resource.dataColorField)(data[dataIndex]),
                        field: resource.field,
                        title: resource.title,
                        name: resource.name,
                        value: kendo.getter(resource.dataValueField)(data[dataIndex])
                    }),
                    className: "k-slot-cell"
                };
                obj[name] = createLayoutConfiguration(name, resources.slice(1), inner, template);

                configuration.push(obj);
            }
            return configuration;
        }
        return inner;
    }

    function groupEqFilter(value) {
        return function (item) {
            if ($.isArray(item) || item instanceof kendo.data.ObservableArray) {
                for (var idx = 0; idx < item.length; idx++) {
                    if (item[idx] == value) {
                        return true;
                    }
                }
                return false;
            }
            return item == value;
        };
    }

    var selectedStateRegExp = /\s*k-state-selected/;
    function addSelectedState(cell) {
        cell.className = cell.className.replace(selectedStateRegExp, "") + " k-state-selected";
    }

    $.extend(ui.SchedulerView, {
        createColumns: createColumns,
        createRows: createRows,
        rangeIndex: rangeIndex,
        collidingEvents: collidingEvents,
        groupEqFilter: groupEqFilter
    });

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        setTime = kendo.date.setTime,
        SchedulerView = ui.SchedulerView,
        extend = $.extend,
        proxy = $.proxy,
        getDate = kendo.date.getDate,
        MS_PER_MINUTE = kendo.date.MS_PER_MINUTE,
        MS_PER_DAY = kendo.date.MS_PER_DAY,
        getMilliseconds = kendo.date.getMilliseconds,
        NS = ".kendoMultiDayView";

    var DAY_VIEW_EVENT_TEMPLATE = kendo.template('<div title="(#=kendo.format("{0:t} - {1:t}", start, end)#): #=title.replace(/"/g,"&\\#34;")#">' +
                    '<div class="k-event-template k-event-time">#:kendo.format("{0:t} - {1:t}", start, end)#</div>' +
                    '<div class="k-event-template">${title}</div>' +
                '</div>'),
        DAY_VIEW_ALL_DAY_EVENT_TEMPLATE = kendo.template('<div title="(#=kendo.format("{0:t}", start)#): #=title.replace(/"/g,"&\\#34;")#">' +
                    '<div class="k-event-template">${title}</div>' +
                '</div>'),
        DATA_HEADER_TEMPLATE = kendo.template("<span class='k-link k-nav-day'>#=kendo.toString(date, 'ddd M/dd')#</span>"),
        ALLDAY_EVENT_WRAPPER_STRING = '<div role="gridcell" aria-selected="false" ' +
                'data-#=ns#uid="#=uid#"' +
                '#if (resources[0]) { #' +
                    'style="background-color:#=resources[0].color#; border-color: #=resources[0].color#"' +
                    'class="k-event#=inverseColor ? " k-event-inverse" : ""#" ' +
                '#} else {#' +
                    'class="k-event"' +
                '#}#' +
                '>' +
                '<span class="k-event-actions">' +
                    '# if(data.tail || data.middle) {#' +
                        '<span class="k-icon k-i-arrow-w"></span>' +
                    '#}#' +
                    '# if(data.isException()) {#' +
                        '<span class="k-icon k-i-exception"></span>' +
                    '# } else if(data.isRecurring()) {#' +
                        '<span class="k-icon k-i-refresh"></span>' +
                    '# } #' +
                '</span>' +
                '{0}' +
                '<span class="k-event-actions">' +
                    '#if (showDelete) {#' +
                        '<a href="\\#" class="k-link k-event-delete"><span class="k-icon k-si-close"></span></a>' +
                    '#}#' +
                    '# if(data.head || data.middle) {#' +
                        '<span class="k-icon k-i-arrow-e"></span>' +
                    '#}#' +
                '</span>' +
                '#if(resizable && !singleDay && !data.tail && !data.middle){#' +
                '<span class="k-resize-handle k-resize-w"></span>' +
                '#}#' +
                '#if(resizable && !singleDay && !data.head && !data.middle){#' +
                '<span class="k-resize-handle k-resize-e"></span>' +
                '#}#' +
                '</div>',
        EVENT_WRAPPER_STRING = '<div role="gridcell" aria-selected="false" ' +
                'data-#=ns#uid="#=uid#" ' +
                '#if (resources[0]) { #' +
                    'style="background-color:#=resources[0].color #; border-color: #=resources[0].color#"' +
                    'class="k-event#=inverseColor ? " k-event-inverse" : ""#"' +
                '#} else {#' +
                    'class="k-event"' +
                '#}#' +
                '>' +
                 '<span class="k-event-actions">' +
                    '# if(data.isException()) {#' +
                        '<span class="k-icon k-i-exception"></span>' +
                    '# } else if(data.isRecurring()) {#' +
                        '<span class="k-icon k-i-refresh"></span>' +
                    '# } #' +
                '</span>' +
                '{0}' +
                '<span class="k-event-actions">' +
                    '#if (showDelete) {#' +
                        '<a href="\\#" class="k-link k-event-delete"><span class="k-icon k-si-close"></span></a>' +
                    '#}#' +
                '</span>' +
                '<span class="k-event-top-actions">' +
                    '# if(data.tail || data.middle) {#' +
                    '<span class="k-icon k-i-arrow-n"></span>' +
                    '# } #' +
                '</span>' +
                '<span class="k-event-bottom-actions">' +
                    '# if(data.head || data.middle) {#' +
                        '<span class="k-icon k-i-arrow-s"></span>' +
                    '# } #' +
                '</span>' +
                '# if(resizable && !data.tail && !data.middle) {#' +
                '<span class="k-resize-handle k-resize-n"></span>' +
                '# } #' +
                '# if(resizable && !data.head && !data.middle) {#' +
                    '<span class="k-resize-handle k-resize-s"></span>' +
                '# } #' +
                '</div>';

    function toInvariantTime(date) {
        var staticDate = new Date(1980, 1, 1, 0, 0, 0);
        setTime(staticDate, getMilliseconds(date));
        return staticDate;
    }

    function isInDateRange(value, min, max) {
        return value >= min && value <= max;
    }

    function isInTimeRange(value, min, max, overlaps) {
        overlaps = overlaps ? value <= max : value < max;
        return value > min && overlaps;
    }

    function addContinuousEvent(group, range, element, isAllDay) {
        var events = group._continuousEvents;
        var lastEvent = events[events.length - 1];
        var startDate = getDate(range.start.startDate()).getTime();

        //this handles all day event which is over multiple slots but starts
        //after one of the time events
        if (isAllDay && lastEvent &&
            getDate(lastEvent.start.startDate()).getTime() == startDate) {

            var idx = events.length - 1;
            for (; idx > -1; idx--) {
                if (events[idx].isAllDay ||
                        getDate(events[idx].start.startDate()).getTime() < startDate) {
                    break;
                }
            }

            events.splice(idx + 1, 0, {
                element: element,
                isAllDay: true,
                uid: element.attr(kendo.attr("uid")),
                start: range.start,
                end: range.end
            });
        } else {
            events.push({
                element: element,
                isAllDay: isAllDay,
                uid: element.attr(kendo.attr("uid")),
                start: range.start,
                end: range.end
            });
        }
    }

    function getWorkDays(options) {
        var workDays = [];
        var dayIndex = options.workWeekStart;

        workDays.push(dayIndex);

        while (options.workWeekEnd != dayIndex) {
            if (dayIndex > 6) {
                dayIndex -= 7;
            } else {
                dayIndex++;
            }
            workDays.push(dayIndex);
        }
        return workDays;
    }

    var MultiDayView = SchedulerView.extend({
        init: function (element, options) {
            var that = this;

            SchedulerView.fn.init.call(that, element, options);

            that.title = that.options.title || that.options.name;

            that._workDays = getWorkDays(that.options);

            that._templates();

            that._editable();

            that.calculateDateRange();

            that._groups();

            that._currentTime();
        },

        _currentTimeMarkerUpdater: function () {
            var currentTime = new Date();
            var options = this.options;

            if (options.currentTimeMarker.useLocalTimezone === false) {
                var timezone = options.dataSource.options.schema.timezone;

                if (options.dataSource && timezone) {
                    var timezoneOffset = kendo.timezone.offset(currentTime, timezone);
                    currentTime = kendo.timezone.convert(currentTime, currentTime.getTimezoneOffset(), timezoneOffset);
                }
            }

            this.times.find(".k-current-time").remove();

            var groupsCount = !options.group || options.group.orientation == "horizontal" ? 1 : this.groups.length;

            for (var groupIndex = 0; groupIndex < groupsCount; groupIndex++) {
                var currentGroup = this.groups[groupIndex];
                var utcCurrentTime = kendo.date.toUtcTime(currentTime);
                var ranges = currentGroup.timeSlotRanges(utcCurrentTime, utcCurrentTime + 1);
                if (ranges.length === 0) {
                    return;
                }
                var collection = ranges[0].collection;
                var slotElement = collection.slotByStartDate(currentTime);

                if (slotElement) {
                    var element = $("<div class='k-current-time'></div>");
                    element.appendTo(this.times).css({
                        top: Math.round(ranges[0].innerRect(currentTime, new Date(currentTime.getTime() + 1), false).top),
                        height: "1px",
                        right: "1px",
                        left: 0
                    });
                }
            }
        },

        _currentTime: function () {
            var that = this;
            var markerOptions = that.options.currentTimeMarker;

            if (markerOptions !== false && markerOptions.updateInterval !== undefined) {
                var updateInterval = markerOptions.updateInterval;

                that._currentTimeMarkerUpdater();
                that._currentTimeUpdateTimer = setInterval(proxy(this._currentTimeMarkerUpdater, that), updateInterval);
            }
        },

        _updateResizeHint: function (event, groupIndex, startTime, endTime) {
            var multiday = event.isMultiDay();

            var group = this.groups[groupIndex];

            var ranges = group.ranges(startTime, endTime, multiday, event.isAllDay);

            this._removeResizeHint();

            for (var rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) {
                var range = ranges[rangeIndex];
                var start = range.startSlot();

                var width = start.offsetWidth;
                var height = start.clientHeight;
                var top = start.offsetTop;

                if (multiday) {
                    width = range.innerWidth();
                } else {
                    var rect = range.outerRect(startTime, endTime, this.options.snap);
                    top = rect.top;
                    height = rect.bottom - rect.top;
                }

                var hint = SchedulerView.fn._createResizeHint.call(this,
                    start.offsetLeft,
                    top,
                    width,
                    height
                );

                this._resizeHint = this._resizeHint.add(hint);
            }

            var format = "t";
            var container = this.content;

            if (multiday) {
                format = "M/dd";
                container = this.element.find(".k-scheduler-header-wrap:has(.k-scheduler-header-all-day) > div");
                if (!container.length) {
                    container = this.content;
                }
            }

            this._resizeHint.appendTo(container);

            this._resizeHint.find(".k-label-top,.k-label-bottom").text("");

            this._resizeHint.first().addClass("k-first").find(".k-label-top").text(kendo.toString(kendo.timezone.toLocalDate(startTime), format));

            this._resizeHint.last().addClass("k-last").find(".k-label-bottom").text(kendo.toString(kendo.timezone.toLocalDate(endTime), format));
        },

        _updateMoveHint: function (event, groupIndex, distance) {
            var multiday = event.isMultiDay();

            var group = this.groups[groupIndex];

            var start = kendo.date.toUtcTime(event.start) + distance;

            var end = start + event.duration();

            var ranges = group.ranges(start, end, multiday, event.isAllDay);

            start = kendo.timezone.toLocalDate(start);

            end = kendo.timezone.toLocalDate(end);

            this._removeMoveHint();

            if (!multiday && (getMilliseconds(end) === 0 || getMilliseconds(end) < getMilliseconds(this.startTime()))) {
                if (ranges.length > 1) {
                    ranges.pop();
                }
            }

            for (var rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) {
                var range = ranges[rangeIndex];
                var startSlot = range.start;

                var hint = this._createEventElement(event.clone({ start: start, end: end }), !multiday);

                hint.addClass("k-event-drag-hint");

                var css = {
                    left: startSlot.offsetLeft + 2,
                    top: startSlot.offsetTop
                };

                if (this._isRtl) {
                    css.left = startSlot.clientWidth * 0.1 + startSlot.offsetLeft + 2;
                }

                if (multiday) {
                    css.width = range.innerWidth() - 4;
                } else {
                    var rect = range.outerRect(start, end, this.options.snap);
                    css.top = rect.top;
                    css.height = rect.bottom - rect.top;
                    css.width = startSlot.clientWidth * 0.9 - 4;
                }

                hint.css(css);

                this._moveHint = this._moveHint.add(hint);
            }

            var content = this.content;

            if (multiday) {
                content = this.element.find(".k-scheduler-header-wrap:has(.k-scheduler-header-all-day) > div");
                if (!content.length) {
                    content = this.content;
                }
            }

            this._moveHint.appendTo(content);
        },

        _slotByPosition: function (x, y) {
            var slot;

            var offset;

            if (this._isVerticallyGrouped()) {
                offset = this.content.offset();
                y += this.content[0].scrollTop;
                x += this.content[0].scrollLeft;
            } else {
                offset = this.element.find(".k-scheduler-header-wrap:has(.k-scheduler-header-all-day)").find(">div").offset();
            }

            if (offset) {
                x -= offset.left;
                y -= offset.top;
            }

            x = Math.ceil(x);
            y = Math.ceil(y);

            var group;
            var groupIndex;

            for (groupIndex = 0; groupIndex < this.groups.length; groupIndex++) {
                group = this.groups[groupIndex];

                slot = group.daySlotByPosition(x, y);

                if (slot) {
                    return slot;
                }
            }

            if (offset) {
                x += offset.left;
                y += offset.top;
            }

            offset = this.content.offset();

            x -= offset.left;
            y -= offset.top;

            if (!this._isVerticallyGrouped()) {
                y += this.content[0].scrollTop;
                x += this.content[0].scrollLeft;
            }

            x = Math.ceil(x);
            y = Math.ceil(y);

            for (groupIndex = 0; groupIndex < this.groups.length; groupIndex++) {
                group = this.groups[groupIndex];

                slot = group.timeSlotByPosition(x, y);

                if (slot) {
                    return slot;
                }
            }

            return null;
        },

        _groupCount: function () {
            var resources = this.groupedResources;

            if (resources.length) {
                if (this._groupOrientation() === "vertical") {
                    return this._rowCountForLevel(resources.length - 1);
                } else {
                    return this._columnCountForLevel(resources.length) / this._columnOffsetForResource(resources.length);
                }
            }
            return 1;
        },

        _columnCountInResourceView: function () {
            var resources = this.groupedResources;

            if (!resources.length || this._isVerticallyGrouped()) {
                return this._columnCountForLevel(0);
            }

            return this._columnOffsetForResource(resources.length);
        },

        _timeSlotGroups: function (groupCount, columnCount) {
            var interval = this._timeSlotInterval();

            var tableRows = this.content.find("tr:not(.k-scheduler-header-all-day)");

            tableRows.attr("role", "row");

            var rowCount = tableRows.length;

            if (this._isVerticallyGrouped()) {
                rowCount = Math.floor(rowCount / groupCount);
            }

            for (var groupIndex = 0; groupIndex < groupCount; groupIndex++) {
                var rowMultiplier = 0;

                if (this._isVerticallyGrouped()) {
                    rowMultiplier = groupIndex;
                }

                var rowIndex = rowMultiplier * rowCount;
                var time;
                var cellMultiplier = 0;

                if (!this._isVerticallyGrouped()) {
                    cellMultiplier = groupIndex;
                }

                while (rowIndex < (rowMultiplier + 1) * rowCount) {
                    var cells = tableRows[rowIndex].children;
                    var group = this.groups[groupIndex];

                    if (rowIndex % rowCount === 0) {
                        time = getMilliseconds(new Date(+this.startTime()));
                    }

                    for (var cellIndex = cellMultiplier * columnCount; cellIndex < (cellMultiplier + 1) * columnCount; cellIndex++) {
                        var cell = cells[cellIndex];

                        var collectionIndex = cellIndex % columnCount;

                        var collection = group.getTimeSlotCollection(collectionIndex);

                        var currentDate = this._dates[collectionIndex];

                        var currentTime = Date.UTC(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate());

                        var start = currentTime + time;

                        var end = start + interval;

                        cell.setAttribute("role", "gridcell");
                        cell.setAttribute("aria-selected", false);

                        collection.addTimeSlot(cell, start, end);
                    }

                    time += interval;
                    rowIndex++;
                }
            }
        },

        _daySlotGroups: function (groupCount, columnCount) {
            var tableRows;

            if (this._isVerticallyGrouped()) {
                tableRows = this.element.find(".k-scheduler-header-all-day");
            } else {
                tableRows = this.element.find(".k-scheduler-header-all-day tr");
            }

            tableRows.attr("role", "row");

            for (var groupIndex = 0; groupIndex < groupCount; groupIndex++) {
                var rowMultiplier = 0;

                if (this._isVerticallyGrouped()) {
                    rowMultiplier = groupIndex;
                }

                var group = this.groups[groupIndex];

                var collection = group.getDaySlotCollection(0);

                var cells = tableRows[rowMultiplier].children;
                var cellMultiplier = 0;

                if (!this._isVerticallyGrouped()) {
                    cellMultiplier = groupIndex;
                }

                var cellCount = 0;

                for (var cellIndex = cellMultiplier * columnCount; cellIndex < (cellMultiplier + 1) * columnCount; cellIndex++) {
                    var cell = cells[cellIndex];

                    if (cellIndex % columnCount === 0) {
                        cellCount = 0;
                    }

                    var start = this._dates[cellCount];

                    var currentTime = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());

                    cellCount++;

                    cell.setAttribute("role", "gridcell");
                    cell.setAttribute("aria-selected", false);

                    collection.addDaySlot(cell, currentTime, currentTime + kendo.date.MS_PER_DAY);
                }
            }
        },

        _groups: function () {
            var groupCount = this._groupCount();
            var columnCount = this._columnCountInResourceView();

            this.groups = [];

            for (var idx = 0; idx < groupCount; idx++) {
                var view = this._addResourceView(idx);

                for (var columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                    view.addTimeSlotCollection(this._dates[columnIndex], kendo.date.addDays(this._dates[columnIndex], 1));
                }

                if (this.options.allDaySlot) {
                    view.addDaySlotCollection(this._dates[0], kendo.date.addDays(this._dates[this._dates.length - 1], 1));
                }
            }

            this._timeSlotGroups(groupCount, columnCount);

            if (this.options.allDaySlot) {
                this._daySlotGroups(groupCount, columnCount);
            }
        },

        options: {
            name: "MultiDayView",
            selectedDateFormat: "{0:D}",
            allDaySlot: true,
            showWorkHours: false,
            title: "",
            startTime: kendo.date.today(),
            endTime: kendo.date.today(),
            minorTickCount: 2,
            majorTick: 60,
            majorTimeHeaderTemplate: "#=kendo.toString(date, 't')#",
            minorTimeHeaderTemplate: "&nbsp;",
            groupHeaderTemplate: "#=text#",
            slotTemplate: "&nbsp;",
            allDaySlotTemplate: "&nbsp;",
            eventTemplate: DAY_VIEW_EVENT_TEMPLATE,
            allDayEventTemplate: DAY_VIEW_ALL_DAY_EVENT_TEMPLATE,
            dateHeaderTemplate: DATA_HEADER_TEMPLATE,
            editable: true,
            workDayStart: new Date(1980, 1, 1, 8, 0, 0),
            workDayEnd: new Date(1980, 1, 1, 17, 0, 0),
            workWeekStart: 1,
            workWeekEnd: 5,
            footer: {
                command: "workDay"
            },
            messages: {
                allDay: "all day",
                showFullDay: "Show full day",
                showWorkDay: "Show business hours"
            },
            currentTimeMarker: {
                updateInterval: 10000,
                useLocalTimezone: true
            }
        },

        events: ["remove", "add", "edit"],

        _templates: function () {
            var options = this.options,
                settings = extend({}, kendo.Template, options.templateSettings);

            this.eventTemplate = this._eventTmpl(options.eventTemplate, EVENT_WRAPPER_STRING);
            this.allDayEventTemplate = this._eventTmpl(options.allDayEventTemplate, ALLDAY_EVENT_WRAPPER_STRING);

            this.majorTimeHeaderTemplate = kendo.template(options.majorTimeHeaderTemplate, settings);
            this.minorTimeHeaderTemplate = kendo.template(options.minorTimeHeaderTemplate, settings);
            this.dateHeaderTemplate = kendo.template(options.dateHeaderTemplate, settings);
            this.slotTemplate = kendo.template(options.slotTemplate, settings);
            this.allDaySlotTemplate = kendo.template(options.allDaySlotTemplate, settings);
            this.groupHeaderTemplate = kendo.template(options.groupHeaderTemplate, settings);
        },

        _editable: function () {
            if (this.options.editable) {
                if (this._isMobile()) {
                    this._touchEditable();
                } else {
                    this._mouseEditable();
                }
            }
        },

        _mouseEditable: function () {
            var that = this;
            that.element.on("click" + NS, ".k-event a:has(.k-si-close)", function (e) {
                that.trigger("remove", { uid: $(this).closest(".k-event").attr(kendo.attr("uid")) });
                e.preventDefault();
            });

            if (that.options.editable.create !== false) {
                that.element.on("dblclick" + NS, ".k-scheduler-content td", function (e) {
                    if (!$(this).parent().hasClass("k-scheduler-header-all-day")) {
                        var slot = that._slotByPosition(e.pageX, e.pageY);

                        if (slot) {
                            var resourceInfo = that._resourceBySlot(slot);
                            that.trigger("add", { eventInfo: extend({ start: slot.startDate(), end: slot.endDate() }, resourceInfo) });
                        }

                        e.preventDefault();
                    }
                }).on("dblclick" + NS, ".k-scheduler-header-all-day td", function (e) {
                    var slot = that._slotByPosition(e.pageX, e.pageY);
                    if (slot) {
                        var resourceInfo = that._resourceBySlot(slot);
                        that.trigger("add", { eventInfo: extend({}, { isAllDay: true, start: kendo.date.getDate(slot.startDate()), end: kendo.date.getDate(slot.startDate()) }, resourceInfo) });
                    }
                    e.preventDefault();
                });
            }

            if (that.options.editable.update !== false) {
                that.element.on("dblclick" + NS, ".k-event", function (e) {
                    that.trigger("edit", { uid: $(this).closest(".k-event").attr(kendo.attr("uid")) });
                    e.preventDefault();
                });
            }
        },

        _touchEditable: function () {
            var that = this;

            if (that.options.editable.create !== false) {
                that._addUserEvents = new kendo.UserEvents(that.element, {
                    filter: ".k-scheduler-content td",
                    tap: function (e) {
                        if (!$(e.target).parent().hasClass("k-scheduler-header-all-day")) {
                            var slot = that._slotByPosition(e.x.location, e.y.location);

                            if (slot) {
                                var resourceInfo = that._resourceBySlot(slot);
                                that.trigger("add", { eventInfo: extend({ start: slot.startDate(), end: slot.endDate() }, resourceInfo) });
                            }

                            e.preventDefault();
                        }
                    }
                });

                that._allDayUserEvents = new kendo.UserEvents(that.element, {
                    filter: ".k-scheduler-header-all-day td",
                    tap: function (e) {
                        var slot = that._slotByPosition(e.x.location, e.y.location);

                        if (slot) {
                            var resourceInfo = that._resourceBySlot(slot);
                            that.trigger("add", { eventInfo: extend({}, { isAllDay: true, start: kendo.date.getDate(slot.startDate()), end: kendo.date.getDate(slot.startDate()) }, resourceInfo) });
                        }

                        e.preventDefault();
                    }
                });
            }

            if (that.options.editable.update !== false) {
                that._editUserEvents = new kendo.UserEvents(that.element, {
                    filter: ".k-event",
                    tap: function (e) {
                        var eventElement = $(e.target).closest(".k-event");

                        if (!eventElement.hasClass("k-event-active")) {
                            that.trigger("edit", { uid: eventElement.attr(kendo.attr("uid")) });
                        }

                        e.preventDefault();
                    }
                });
            }
        },

        _layout: function (dates) {
            var columns = [];
            var rows = [];
            var options = this.options;
            var that = this;

            for (var idx = 0; idx < dates.length; idx++) {
                var column = {};

                column.text = that.dateHeaderTemplate({ date: dates[idx] });

                if (kendo.date.isToday(dates[idx])) {
                    column.className = "k-today";
                }

                columns.push(column);
            }

            var resources = this.groupedResources;

            if (options.allDaySlot) {
                rows.push({
                    text: options.messages.allDay, allDay: true,
                    cellContent: function (idx) {
                        var groupIndex = idx;

                        idx = resources.length && that._groupOrientation() !== "vertical" ? idx % dates.length : idx;

                        return that.allDaySlotTemplate({ date: dates[idx], resources: function () {
                            return that._resourceBySlot({ groupIndex: groupIndex });
                        }
                        });
                    }
                });
            }

            this._forTimeRange(this.startTime(), this.endTime(), function (date, majorTick, middleRow, lastSlotRow) {
                var template = majorTick ? that.majorTimeHeaderTemplate : that.minorTimeHeaderTemplate;

                var row = {
                    text: template({ date: date }),
                    className: lastSlotRow ? "k-slot-cell" : ""
                };

                rows.push(row);
            });


            if (resources.length) {
                if (this._groupOrientation() === "vertical") {
                    rows = this._createRowsLayout(resources, rows, this.groupHeaderTemplate);
                } else {
                    columns = this._createColumnsLayout(resources, columns, this.groupHeaderTemplate);
                }
            }

            return {
                columns: columns,
                rows: rows
            };
        },

        _footer: function () {
            var options = this.options;

            if (options.footer !== false) {
                var html = '<div class="k-header k-scheduler-footer">';

                var command = options.footer.command;

                if (command && command === "workDay") {
                    html += '<ul class="k-reset k-header">';

                    html += '<li class="k-state-default k-scheduler-fullday"><a href="#" class="k-link"><span class="k-icon k-i-clock"></span>';
                    html += (options.showWorkHours ? options.messages.showFullDay : options.messages.showWorkDay) + '</a></li>';

                    html += '</ul>';

                } else {
                    html += "&nbsp;";
                }

                html += "</div>";

                this.footer = $(html).appendTo(this.element);

                var that = this;

                this.footer.on("click" + NS, ".k-scheduler-fullday", function (e) {
                    e.preventDefault();
                    that.trigger("navigate", { view: that.name || options.name, date: that.startDate(), isWorkDay: !options.showWorkHours });
                });
            }
        },

        _forTimeRange: function (min, max, action, after) {
            min = toInvariantTime(min); //convert the date to 1/2/1980 and sets the time
            max = toInvariantTime(max);

            var that = this,
                msMin = getMilliseconds(min),
                msMax = getMilliseconds(max),
                minorTickCount = that.options.minorTickCount,
                msMajorInterval = that.options.majorTick * MS_PER_MINUTE,
                msInterval = msMajorInterval / minorTickCount || 1,
                start = new Date(+min),
                startDay = start.getDate(),
                msStart,
                idx = 0, length,
                html = "";

            length = MS_PER_DAY / msInterval;

            if (msMin != msMax) {
                if (msMin > msMax) {
                    msMax += MS_PER_DAY;
                }

                length = ((msMax - msMin) / msInterval);
            }

            length = Math.round(length);

            for (; idx < length; idx++) {
                var majorTickDivider = idx % (msMajorInterval / msInterval),
                    isMajorTickRow = majorTickDivider === 0,
                    isMiddleRow = majorTickDivider < minorTickCount - 1,
                    isLastSlotRow = majorTickDivider === minorTickCount - 1;

                html += action(start, isMajorTickRow, isMiddleRow, isLastSlotRow);

                setTime(start, msInterval, false);
            }

            if (msMax) {
                msStart = getMilliseconds(start);
                if (startDay < start.getDate()) {
                    msStart += MS_PER_DAY;
                }

                if (msStart > msMax) {
                    start = new Date(+max);
                }
            }

            if (after) {
                html += after(start);
            }

            return html;
        },

        _content: function (dates) {
            var that = this;
            var options = that.options;
            var start = that.startTime();
            var end = this.endTime();
            var groupsCount = 1;
            var rowCount = 1;
            var columnCount = dates.length;
            var html = '';
            var resources = this.groupedResources;
            var slotTemplate = this.slotTemplate;
            var allDaySlotTemplate = this.allDaySlotTemplate;
            var isVerticalGroupped = false;
            var allDayVerticalGroupRow;

            if (resources.length) {
                isVerticalGroupped = that._groupOrientation() === "vertical";

                if (isVerticalGroupped) {
                    rowCount = this._rowCountForLevel(this.rowLevels.length - 2);
                    if (options.allDaySlot) {
                        allDayVerticalGroupRow = function (groupIndex) {
                            var result = '<tr class="k-scheduler-header-all-day">';
                            var resources = function () {
                                return that._resourceBySlot({ groupIndex: groupIndex });
                            };

                            for (var idx = 0, length = dates.length; idx < length; idx++) {
                                result += "<td>" + allDaySlotTemplate({ date: dates[idx], resources: resources }) + "</td>";
                            }

                            return result + "</tr>";
                        };
                    }
                } else {
                    groupsCount = this._columnCountForLevel(this.columnLevels.length - 2);
                }
            }

            html += '<tbody>';

            var appendRow = function (date, majorTick) {
                var content = "";
                var idx;
                var length;
                var classes = "";
                var tmplDate;
                var groupIdx = 0;

                content = '<tr' + (majorTick ? ' class="k-middle-row"' : "") + '>';

                var resources = function (groupIndex) {
                    return function () {
                        return that._resourceBySlot({ groupIndex: groupIndex });
                    };
                };

                for (; groupIdx < groupsCount; groupIdx++) {
                    for (idx = 0, length = columnCount; idx < length; idx++) {
                        classes = "";

                        if (kendo.date.isToday(dates[idx])) {
                            classes += "k-today";
                        }

                        if (kendo.date.getMilliseconds(date) < kendo.date.getMilliseconds(that.options.workDayStart) ||
                            kendo.date.getMilliseconds(date) >= kendo.date.getMilliseconds(that.options.workDayEnd) ||
                            !that._isWorkDay(dates[idx])) {
                            classes += " k-nonwork-hour";
                        }

                        content += '<td' + (classes !== "" ? ' class="' + classes + '"' : "") + ">";
                        tmplDate = kendo.date.getDate(dates[idx]);
                        kendo.date.setTime(tmplDate, kendo.date.getMilliseconds(date));

                        content += slotTemplate({ date: tmplDate, resources: resources(isVerticalGroupped ? rowIdx : groupIdx) });
                        content += "</td>";
                    }
                }

                content += "</tr>";

                return content;
            };

            for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
                html += allDayVerticalGroupRow ? allDayVerticalGroupRow(rowIdx) : "";

                html += this._forTimeRange(start, end, appendRow);
            }

            html += '</tbody>';

            this.content.find("table").append(html);
        },

        _isWorkDay: function (date) {
            var day = date.getDay();
            var workDays = this._workDays;

            for (var i = 0; i < workDays.length; i++) {
                if (workDays[i] === day) {
                    return true;
                }
            }
            return false;
        },

        _render: function (dates) {
            var that = this;

            dates = dates || [];

            this._dates = dates;

            this._startDate = dates[0];

            this._endDate = dates[(dates.length - 1) || 0];

            this.createLayout(this._layout(dates));

            this._content(dates);

            this._footer();

            this.refreshLayout();

            var allDayHeader = this.element.find(".k-scheduler-header-all-day td");

            if (allDayHeader.length) {
                this._allDayHeaderHeight = allDayHeader.first()[0].clientHeight;
            }

            that.datesHeader.on("click" + NS, ".k-nav-day", function (e) {
                var th = $(e.currentTarget).closest("th");

                var offset = th.offset();

                var slot = that._slotByPosition(offset.left, offset.top + th.outerHeight());

                that.trigger("navigate", { view: "day", date: slot.startDate() });
            });
        },

        startTime: function () {
            var options = this.options;
            return options.showWorkHours ? options.workDayStart : options.startTime;
        },

        endTime: function () {
            var options = this.options;
            return options.showWorkHours ? options.workDayEnd : options.endTime;
        },

        startDate: function () {
            return this._startDate;
        },

        endDate: function () {
            return this._endDate;
        },

        _end: function (isAllDay) {
            var time = getMilliseconds(this.endTime()) || MS_PER_DAY;

            if (isAllDay) {
                time = 0;
            }

            return new Date(this._endDate.getTime() + time);
        },

        nextDate: function () {
            return kendo.date.nextDay(this.endDate());
        },

        previousDate: function () {
            return kendo.date.previousDay(this.startDate());
        },

        calculateDateRange: function () {
            this._render([this.options.date]);
        },

        destroy: function () {
            var that = this;

            if (that._currentTimeUpdateTimer) {
                clearInterval(that._currentTimeUpdateTimer);
            }

            if (that.datesHeader) {
                that.datesHeader.off(NS);
            }

            if (that.element) {
                that.element.off(NS);
            }

            if (that.footer) {
                that.footer.remove();
            }

            SchedulerView.fn.destroy.call(this);

            if (this._isMobile() && that.options.editable) {
                if (that.options.editable.create !== false) {
                    that._addUserEvents.destroy();
                    that._allDayUserEvents.destroy();
                }

                if (that.options.editable.update !== false) {
                    that._editUserEvents.destroy();
                }
            }
        },

        inRange: function (options) {
            var inRange = SchedulerView.fn.inRange.call(this, options);

            var startTime = getMilliseconds(this.startTime());
            var endTime = getMilliseconds(this.endTime()) || kendo.date.MS_PER_DAY;

            var start = getMilliseconds(options.start);
            var end = getMilliseconds(options.end) || kendo.date.MS_PER_DAY;

            return inRange && startTime <= start && end <= endTime;
        },

        selectionByElement: function (cell) {
            var offset = cell.offset();
            return this._slotByPosition(offset.left, offset.top);
        },

        _timeSlotInterval: function () {
            var options = this.options;
            return (options.majorTick / options.minorTickCount) * MS_PER_MINUTE;
        },

        _timeSlotIndex: function (date) {
            var options = this.options;
            var eventStartTime = getMilliseconds(date);
            var startTime = getMilliseconds(this.startTime());
            var timeSlotInterval = ((options.majorTick / options.minorTickCount) * MS_PER_MINUTE);

            return (eventStartTime - startTime) / (timeSlotInterval);
        },

        _slotIndex: function (date, multiday) {
            if (multiday) {
                return this._dateSlotIndex(date);
            }

            return this._timeSlotIndex(date);
        },

        _dateSlotIndex: function (date, overlaps) {
            var idx;
            var length;
            var slots = this._dates || [];
            var slotStart;
            var slotEnd;
            var offset = 1;

            for (idx = 0, length = slots.length; idx < length; idx++) {
                slotStart = kendo.date.getDate(slots[idx]);
                slotEnd = new Date(kendo.date.getDate(slots[idx]).getTime() + MS_PER_DAY - (overlaps ? 0 : 1));

                if (isInDateRange(date, slotStart, slotEnd)) {
                    return idx * offset;
                }
            }
            return -1;
        },

        _positionAllDayEvent: function (element, slotRange) {
            var slotWidth = slotRange.innerWidth();
            var startIndex = slotRange.start.index;
            var endIndex = slotRange.end.index;

            var allDayEvents = SchedulerView.collidingEvents(slotRange.events(), startIndex, endIndex);

            var currentColumnCount = this._headerColumnCount || 0;

            var leftOffset = 2;

            var rightOffset = startIndex !== endIndex ? 5 : 4;

            var eventHeight = this._allDayHeaderHeight;

            var start = slotRange.startSlot();

            element
                .css({
                    left: start.offsetLeft + leftOffset,
                    width: slotWidth - rightOffset
                });

            slotRange.addEvent({ slotIndex: startIndex, start: startIndex, end: endIndex, element: element });

            allDayEvents.push({ slotIndex: startIndex, start: startIndex, end: endIndex, element: element });

            var rows = SchedulerView.createRows(allDayEvents);

            if (rows.length && rows.length > currentColumnCount) {
                this._headerColumnCount = rows.length;
            }

            var top = slotRange.start.offsetTop;

            for (var idx = 0, length = rows.length; idx < length; idx++) {
                var rowEvents = rows[idx].events;

                for (var j = 0, eventLength = rowEvents.length; j < eventLength; j++) {
                    $(rowEvents[j].element).css({
                        top: top + idx * eventHeight
                    });
                }
            }
        },

        _arrangeColumns: function (element, top, height, slotRange) {
            var startSlot = slotRange.start;

            element = { element: element, slotIndex: startSlot.index, start: top, end: top + height };

            var columns,
                slotWidth = startSlot.clientWidth,
                eventRightOffset = slotWidth * 0.10,
                columnEvents,
                eventElements = slotRange.events(),
                slotEvents = SchedulerView.collidingEvents(eventElements, element.start, element.end);

            slotRange.addEvent(element);

            slotEvents.push(element);

            columns = SchedulerView.createColumns(slotEvents);

            var columnWidth = (slotWidth - eventRightOffset) / columns.length;

            for (var idx = 0, length = columns.length; idx < length; idx++) {
                columnEvents = columns[idx].events;

                for (var j = 0, eventLength = columnEvents.length; j < eventLength; j++) {
                    columnEvents[j].element[0].style.width = columnWidth - 4 + "px";
                    columnEvents[j].element[0].style.left = (this._isRtl ? eventRightOffset : 0) + startSlot.offsetLeft + idx * columnWidth + 2 + "px";
                }
            }
        },

        _positionEvent: function (event, element, slotRange) {
            var start = event.startTime || event.start;
            var end = event.endTime || event.end;

            var rect = slotRange.innerRect(start, end, false);

            var height = rect.bottom - rect.top - 2; /* two times border width */

            if (height < 0) {
                height = 0;
            }

            element.css({
                top: rect.top,
                height: height
            });

            this._arrangeColumns(element, rect.top, element[0].clientHeight, slotRange);
        },

        _createEventElement: function (event, isOneDayEvent, head, tail) {
            var template = isOneDayEvent ? this.eventTemplate : this.allDayEventTemplate;
            var options = this.options;
            var editable = options.editable;
            var isMobile = this._isMobile();
            var showDelete = editable && editable.destroy !== false && !isMobile;
            var resizable = editable && editable.resize !== false;
            var startDate = getDate(this.startDate());
            var endDate = getDate(this.endDate());
            var startTime = getMilliseconds(this.startTime());
            var endTime = getMilliseconds(this.endTime());
            var eventStartTime = event._time("start");
            var eventEndTime = event._time("end");
            var middle;

            if (startTime >= endTime) {
                endTime = getMilliseconds(new Date(this.endTime().getTime() + MS_PER_DAY - 1));
            }

            if (!isOneDayEvent && !event.isAllDay) {
                endDate = new Date(endDate.getTime() + MS_PER_DAY);
            }

            var eventStartDate = event.start;
            var eventEndDate = event.end;

            if (event.isAllDay) {
                eventEndDate = getDate(event.end);
            }

            if ((!isInDateRange(getDate(eventStartDate), startDate, endDate) &&
                !isInDateRange(eventEndDate, startDate, endDate)) ||
                (isOneDayEvent && eventStartTime < startTime && eventEndTime > endTime)) {

                middle = true;
            } else if (getDate(eventStartDate) < startDate || (isOneDayEvent && eventStartTime < startTime)) {
                tail = true;
            } else if ((eventEndDate > endDate && !isOneDayEvent) || (isOneDayEvent && eventEndTime > endTime)) {
                head = true;
            }

            var resources = this.eventResources(event);

            if (event.startTime) {
                eventStartDate = new Date(eventStartTime);
                eventStartDate = kendo.timezone.apply(eventStartDate, "Etc/UTC");
            }

            if (event.endTime) {
                eventEndDate = new Date(eventEndTime);
                eventEndDate = kendo.timezone.apply(eventEndDate, "Etc/UTC");
            }

            var data = extend({}, {
                ns: kendo.ns,
                resizable: resizable,
                showDelete: showDelete,
                middle: middle,
                head: head,
                tail: tail,
                singleDay: this._dates.length == 1,
                resources: resources,
                inverseColor: resources && resources[0] ? this._shouldInverseResourceColor(resources[0]) : false
            }, event, {
                start: eventStartDate,
                end: eventEndDate
            });

            var element = $(template(data));

            this.angular("compile", function () {
                return {
                    elements: element,
                    data: [{ dataItem: data}]
                };
            });

            return element;
        },

        _isInTimeSlot: function (event) {
            var slotStartTime = this.startTime(),
                slotEndTime = this.endTime(),
                startTime = event.startTime || event.start,
                endTime = event.endTime || event.end;

            if (getMilliseconds(slotEndTime) === getMilliseconds(kendo.date.getDate(slotEndTime))) {
                slotEndTime = kendo.date.getDate(slotEndTime);
                setTime(slotEndTime, MS_PER_DAY - 1);
            }

            if (event._date("end") > event._date("start")) {
                endTime = +event._date("end") + (MS_PER_DAY - 1);
            }

            endTime = endTime - event._date("end");
            startTime = startTime - event._date("start");
            slotEndTime = getMilliseconds(slotEndTime);
            slotStartTime = getMilliseconds(slotStartTime);

            if (slotStartTime === startTime && startTime === endTime) {
                return true;
            }

            var overlaps = startTime !== slotEndTime;

            return isInTimeRange(startTime, slotStartTime, slotEndTime, overlaps) ||
                isInTimeRange(endTime, slotStartTime, slotEndTime, overlaps) ||
                isInTimeRange(slotStartTime, startTime, endTime) ||
                isInTimeRange(slotEndTime, startTime, endTime);
        },

        _isInDateSlot: function (event) {
            var groups = this.groups[0];
            var slotStart = groups.firstSlot().start;
            var slotEnd = groups.lastSlot().end - 1;

            var startTime = kendo.date.toUtcTime(event.start);
            var endTime = kendo.date.toUtcTime(event.end);

            return (isInDateRange(startTime, slotStart, slotEnd) ||
                isInDateRange(endTime, slotStart, slotEnd) ||
                isInDateRange(slotStart, startTime, endTime) ||
                isInDateRange(slotEnd, startTime, endTime)) &&
                (!isInDateRange(endTime, slotStart, slotStart) || isInDateRange(endTime, startTime, startTime) || event.isAllDay);
        },

        _updateAllDayHeaderHeight: function (height) {
            if (this._height !== height) {
                this._height = height;

                var allDaySlots = this.element.find(".k-scheduler-header-all-day td");

                if (allDaySlots.length) {
                    allDaySlots.parent()
                        .add(this.element.find(".k-scheduler-times-all-day").parent())
                        .height(height);

                    for (var groupIndex = 0; groupIndex < this.groups.length; groupIndex++) {
                        this.groups[groupIndex].refresh();
                    }
                }
            }
        },

        _renderEvents: function (events, groupIndex) {
            var allDayEventContainer = this.datesHeader.find(".k-scheduler-header-wrap > div");
            var event;

            var idx;
            var length;

            for (idx = 0, length = events.length; idx < length; idx++) {
                event = events[idx];

                if (this._isInDateSlot(event)) {

                    var isMultiDayEvent = event.isAllDay || event.end.getTime() - event.start.getTime() >= MS_PER_DAY;
                    var container = isMultiDayEvent && !this._isVerticallyGrouped() ? allDayEventContainer : this.content;
                    var element;
                    var ranges;
                    var group;

                    if (!isMultiDayEvent) {

                        if (this._isInTimeSlot(event)) {
                            group = this.groups[groupIndex];

                            if (!group._continuousEvents) {
                                group._continuousEvents = [];
                            }

                            ranges = group.slotRanges(event);

                            var rangeCount = ranges.length;

                            for (var rangeIndex = 0; rangeIndex < rangeCount; rangeIndex++) {
                                var range = ranges[rangeIndex];
                                var start = event.start;
                                var end = event.end;

                                if (rangeCount > 1) {
                                    if (rangeIndex === 0) {
                                        end = range.end.endDate();
                                    } else if (rangeIndex == rangeCount - 1) {
                                        start = range.start.startDate();
                                    } else {
                                        start = range.start.startDate();
                                        end = range.end.endDate();
                                    }
                                }

                                var occurrence = event.clone({ start: start, end: end, startTime: event.startTime, endTime: event.endTime });

                                if (this._isInTimeSlot(occurrence)) {
                                    var head = range.head;

                                    element = this._createEventElement(event, !isMultiDayEvent, head, range.tail);

                                    element.appendTo(container);

                                    this._positionEvent(occurrence, element, range);

                                    addContinuousEvent(group, range, element, false);
                                }
                            }
                        }

                    } else if (this.options.allDaySlot) {
                        group = this.groups[groupIndex];

                        if (!group._continuousEvents) {
                            group._continuousEvents = [];
                        }

                        ranges = group.slotRanges(event);

                        if (ranges.length) {
                            element = this._createEventElement(event, !isMultiDayEvent);

                            this._positionAllDayEvent(element, ranges[0]);

                            addContinuousEvent(group, ranges[0], element, true);

                            element.appendTo(container);
                        }
                    }
                }
            }
        },

        render: function (events) {
            this._headerColumnCount = 0;

            this._groups();

            this.element.find(".k-event").remove();

            events = new kendo.data.Query(events)
                .sort([{ field: "start", dir: "asc" }, { field: "end", dir: "desc"}])
                .toArray();

            var eventsByResource = [];

            this._eventsByResource(events, this.groupedResources, eventsByResource);

            var that = this;

            var eventsPerDate = $.map(this._dates, function (date) {
                return Math.max.apply(null,
                    $.map(eventsByResource, function (events) {
                        return $.grep(events, function (event) {
                            return event.isMultiDay() && isInDateRange(date, getDate(event.start), getDate(event.end));
                        }).length;
                    })
                );
            });

            var height = Math.max.apply(null, eventsPerDate);

            this._updateAllDayHeaderHeight((height + 1) * this._allDayHeaderHeight);

            for (var groupIndex = 0; groupIndex < eventsByResource.length; groupIndex++) {
                this._renderEvents(eventsByResource[groupIndex], groupIndex);
            }

            this.refreshLayout();
            this.trigger("activate");
        },

        _eventsByResource: function (events, resources, result) {
            var resource = resources[0];

            if (resource) {
                var view = resource.dataSource.view();

                for (var itemIdx = 0; itemIdx < view.length; itemIdx++) {
                    var value = this._resourceValue(resource, view[itemIdx]);

                    var eventsFilteredByResource = new kendo.data.Query(events).filter({ field: resource.field, operator: SchedulerView.groupEqFilter(value) }).toArray();

                    if (resources.length > 1) {
                        this._eventsByResource(eventsFilteredByResource, resources.slice(1), result);
                    } else {
                        result.push(eventsFilteredByResource);
                    }
                }
            } else {
                result.push(events);
            }
        },

        _columnOffsetForResource: function (index) {
            return this._columnCountForLevel(index) / this._columnCountForLevel(index - 1);
        },

        _columnCountForLevel: function (level) {
            var columnLevel = this.columnLevels[level];
            return columnLevel ? columnLevel.length : 0;
        },

        _rowCountForLevel: function (level) {
            var rowLevel = this.rowLevels[level];
            return rowLevel ? rowLevel.length : 0;
        },

        clearSelection: function () {

            this.content.add(this.datesHeader)
                .find(".k-state-selected")
                .removeAttr("id")
                .attr("aria-selected", false)
                .removeClass("k-state-selected");
        },

        _updateDirection: function (selection, ranges, multiple, reverse, vertical) {
            var isDaySlot = selection.isAllDay;
            var startSlot = ranges[0].start;
            var endSlot = ranges[ranges.length - 1].end;

            if (multiple) {
                if (vertical) {
                    if (!isDaySlot &&
                        startSlot.index === endSlot.index &&
                        startSlot.collectionIndex === endSlot.collectionIndex) {
                        selection.backward = reverse;
                    }
                } else {
                    if ((isDaySlot && startSlot.index === endSlot.index) ||
                        (!isDaySlot && startSlot.collectionIndex === endSlot.collectionIndex)) {
                        selection.backward = reverse;
                    }
                }
            }
        },

        _changeViewPeriod: function (selection, reverse, vertical) {
            if (!vertical) {
                var date = reverse ? this.previousDate() : this.nextDate();
                var start = selection.start;
                var end = selection.end;

                selection.start = new Date(date);
                selection.end = new Date(date);

                var endMilliseconds = selection.isAllDay ? MS_PER_DAY : getMilliseconds(end);

                setTime(selection.start, getMilliseconds(start));
                setTime(selection.end, endMilliseconds);

                if (!this._isVerticallyGrouped()) {
                    selection.groupIndex = reverse ? this.groups.length - 1 : 0;
                }

                selection.events = [];

                return true;
            }
        }
    });

    extend(true, ui, {
        MultiDayView: MultiDayView,
        DayView: MultiDayView.extend({
            options: {
                name: "DayView",
                title: "Day"
            },
            name: "day"
        }),
        WeekView: MultiDayView.extend({
            options: {
                name: "WeekView",
                title: "Week",
                selectedDateFormat: "{0:D} - {1:D}"
            },
            name: "week",
            calculateDateRange: function () {
                var selectedDate = this.options.date,
                    start = kendo.date.dayOfWeek(selectedDate, this.calendarInfo().firstDay, -1),
                    idx, length,
                    dates = [];

                for (idx = 0, length = 7; idx < length; idx++) {
                    dates.push(start);
                    start = kendo.date.nextDay(start);
                }
                this._render(dates);
            }
        }),
        WorkWeekView: MultiDayView.extend({
            options: {
                name: "WorkWeekView",
                title: "Work Week",
                selectedDateFormat: "{0:D} - {1:D}"
            },
            name: "workWeek",
            nextDate: function () {
                return kendo.date.dayOfWeek(kendo.date.nextDay(this.endDate()), this.options.workWeekStart, 1);
            },
            previousDate: function () {
                return kendo.date.previousDay(this.startDate());
            },
            calculateDateRange: function () {
                var selectedDate = this.options.date,
                    start = kendo.date.dayOfWeek(selectedDate, this.options.workWeekStart, -1),
                    end = kendo.date.dayOfWeek(start, this.options.workWeekEnd, 1),
                    dates = [];

                while (start <= end) {
                    dates.push(start);
                    start = kendo.date.nextDay(start);
                }
                this._render(dates);
            }
        })
    });

})(window.kendo.jQuery);





(function ($) {
    var kendo = window.kendo,
        ui = kendo.ui,
        NS = ".kendoAgendaView";

    var EVENT_WRAPPER_FORMAT = '<div class="k-task" title="#:title.replace(/"/g,"\'")#" data-#=kendo.ns#uid="#=uid#">' +
                               '# if (resources[0]) {#' +
                               '<span class="k-scheduler-mark" style="background-color:#=resources[0].color#"></span>' +
                               "# } #" +
                               "# if (data.isException()) { #" +
                               '<span class="k-icon k-i-exception"></span>' +
                               '# } else if (data.isRecurring()) {#' +
                               '<span class="k-icon k-i-refresh"></span>' +
                               "# } #" +
                               '{0}' +
                               '#if (showDelete) {#' +
                                   '<a href="\\#" class="k-link k-event-delete"><span class="k-icon k-si-close"></span></a>' +
                               '#}#' +
                           '</div>';

    ui.AgendaView = ui.SchedulerView.extend({
        init: function (element, options) {
            ui.SchedulerView.fn.init.call(this, element, options);

            options = this.options;

            if (options.editable) {
                options.editable = $.extend(
                    { "delete": true },
                    options.editable,
                    { create: false, update: false }
                );
            }

            this.title = options.title;

            this.name = "agenda";

            this._eventTemplate = this._eventTmpl(options.eventTemplate, EVENT_WRAPPER_FORMAT);
            this._dateTemplate = kendo.template(options.eventDateTemplate);
            this._groupTemplate = kendo.template(options.eventGroupTemplate);
            this._timeTemplate = kendo.template(options.eventTimeTemplate);

            this.element.on("mouseenter" + NS, ".k-scheduler-agenda .k-scheduler-content tr", "_mouseenter")
                        .on("mouseleave" + NS, ".k-scheduler-agenda .k-scheduler-content tr", "_mouseleave")
                        .on("click" + NS, ".k-scheduler-agenda .k-scheduler-content .k-link:has(.k-si-close)", "_remove");

            this._renderLayout(options.date);
        },

        _mouseenter: function (e) {
            $(e.currentTarget).addClass("k-state-hover");
        },

        _mouseleave: function (e) {
            $(e.currentTarget).removeClass("k-state-hover");
        },

        _remove: function (e) {
            e.preventDefault();

            this.trigger("remove", {
                uid: $(e.currentTarget).closest(".k-task").attr(kendo.attr("uid"))
            });
        },

        nextDate: function () {
            return kendo.date.nextDay(this.startDate());
        },

        startDate: function () {
            return this._startDate;
        },

        endDate: function () {
            return this._endDate;
        },

        previousDate: function () {
            return kendo.date.previousDay(this.startDate());
        },

        _renderLayout: function (date) {
            this._startDate = date;
            this._endDate = kendo.date.addDays(date, 7);
            this.createLayout(this._layout());
            this.table.addClass("k-scheduler-agenda");
        },

        _layout: function () {
            var columns = [
                    { text: this.options.messages.time, className: "k-scheduler-timecolumn" },
                    { text: this.options.messages.event }
                ];

            if (!this._isMobilePhoneView()) {
                columns.splice(0, 0, { text: this.options.messages.date, className: "k-scheduler-datecolumn" });
            }

            var resources = this.groupedResources;
            if (resources.length) {
                var groupHeaders = [];
                for (var idx = 0; idx < resources.length; idx++) {
                    groupHeaders.push({ text: "", className: "k-scheduler-groupcolumn" });
                }

                columns = groupHeaders.concat(columns);
            }

            return {
                columns: columns
            };
        },

        _tasks: function (events) {
            var tasks = [];

            for (var idx = 0; idx < events.length; idx++) {
                var event = events[idx];
                var start = event.start;
                var end = event.end;

                var eventDurationInDays =
                    (kendo.date.getDate(end) - kendo.date.getDate(start)) / kendo.date.MS_PER_DAY + 1;

                var task = event.clone();
                task.startDate = kendo.date.getDate(start);

                if (task.startDate >= this.startDate()) {
                    tasks.push(task);
                }

                if (eventDurationInDays > 1) {
                    task.end = kendo.date.nextDay(start);
                    task.head = true;
                    for (var day = 1; day < eventDurationInDays; day++) {
                        start = task.end;
                        task = event.clone();
                        task.start = start;
                        task.startDate = kendo.date.getDate(start);
                        task.end = kendo.date.nextDay(start);
                        if (day == eventDurationInDays - 1) {
                            task.end = new Date(task.start.getFullYear(), task.start.getMonth(), task.start.getDate(), end.getHours(), end.getMinutes(), end.getSeconds(), end.getMilliseconds());
                            task.tail = true;
                        } else {
                            task.isAllDay = true;
                            task.middle = true;
                        }

                        if (task.end <= this.endDate() && task.start >= this.startDate()) {
                            tasks.push(task);
                        }
                    }
                }
            }

            return new kendo.data.Query(tasks).sort([{ field: "start", dir: "asc" }, { field: "end", dir: "asc"}]).groupBy({ field: "startDate" }).toArray();
        },

        _renderTaskGroups: function (tasksGroups, groups) {
            var tableRows = [];
            var editable = this.options.editable;
            var showDelete = editable && editable.destroy !== false && !this._isMobile();
            var isPhoneView = this._isMobilePhoneView();

            for (var taskGroupIndex = 0; taskGroupIndex < tasksGroups.length; taskGroupIndex++) {
                var date = tasksGroups[taskGroupIndex].value;

                var tasks = tasksGroups[taskGroupIndex].items;

                var today = kendo.date.isToday(date);

                for (var taskIndex = 0; taskIndex < tasks.length; taskIndex++) {
                    var task = tasks[taskIndex];

                    var tableRow = [];

                    var headerCells = !isPhoneView ? tableRow : [];

                    if (taskGroupIndex === 0 && taskIndex === 0 && groups.length) {
                        for (var idx = 0; idx < groups.length; idx++) {
                            headerCells.push(kendo.format(
                                '<td class="k-scheduler-groupcolumn{2}" rowspan="{0}">{1}</td>',
                                groups[idx].rowSpan,
                                this._groupTemplate({ value: groups[idx].text }),
                                groups[idx].className
                            ));
                        }
                    }

                    if (taskIndex === 0) {
                        if (isPhoneView) {
                            headerCells.push(kendo.format(
                                '<td class="k-scheduler-datecolumn" colspan="2">{0}</td>',
                                this._dateTemplate({ date: date })
                            ));

                            tableRows.push('<tr role="row" aria-selected="false"' + (today ? ' class="k-today">' : ">") + headerCells.join("") + "</tr>");
                        } else {
                            tableRow.push(kendo.format(
                                '<td class="k-scheduler-datecolumn{3}{2}" rowspan="{0}">{1}</td>',
                                tasks.length,
                                this._dateTemplate({ date: date }),
                                taskGroupIndex == tasksGroups.length - 1 && !groups.length ? " k-last" : "",
                                !groups.length ? " k-first" : ""
                            ));
                        }
                    }

                    if (task.head) {
                        task.format = "{0:t}";
                    } else if (task.tail) {
                        task.format = "{1:t}";
                    } else {
                        task.format = "{0:t}-{1:t}";
                    }

                    task.resources = this.eventResources(task);

                    tableRow.push(kendo.format(
                        '<td class="k-scheduler-timecolumn"><div>{0}{1}{2}</div></td><td>{3}</td>',
                        task.tail || task.middle ? '<span class="k-icon k-i-arrow-w"></span>' : "",
                        this._timeTemplate(task.clone({ start: task.startTime || task.start, end: task.endTime || task.end })),
                        task.head || task.middle ? '<span class="k-icon k-i-arrow-e"></span>' : "",
                        this._eventTemplate(task.clone({ showDelete: showDelete }))
                    ));

                    tableRows.push('<tr role="row" aria-selected="false"' + (today ? ' class="k-today">' : ">") + tableRow.join("") + "</tr>");
                }
            }

            return tableRows.join("");
        },

        render: function (events) {
            var table = this.content.find("table").empty();
            var groups = [];

            if (events.length > 0) {
                var resources = this.groupedResources;

                if (resources.length) {
                    groups = this._createGroupConfiguration(events, resources, null);
                    this._renderGroups(groups, table, []);
                } else {
                    groups = this._tasks(events);
                    table.append(this._renderTaskGroups(groups, []));
                }
            }

            var items = this._eventsList = flattenTaskGroups(groups);
            this._angularItems(table, items);

            this.refreshLayout();
            this.trigger("activate");
        },

        _angularItems: function (table, items) {
            this.angular("compile", function () {
                var data = [], elements = items.map(function (item) {
                    data.push({ dataItem: item });
                    return table.find(".k-task[" + kendo.attr("uid") + "=" + item.uid + "]");
                });
                return { elements: elements, data: data };
            });
        },

        _renderGroups: function (groups, table, parentGroups) {
            for (var idx = 0, length = groups.length; idx < length; idx++) {
                var parents = parentGroups.splice(0);
                parents.push(groups[idx]);

                if (groups[idx].groups) {
                    this._renderGroups(groups[idx].groups, table, parents);
                } else {
                    table.append(this._renderTaskGroups(groups[idx].items, parents));
                }
            }
        },

        _createGroupConfiguration: function (events, resources, parent) {
            var resource = resources[0];
            var configuration = [];
            var data = resource.dataSource.view();
            var isPhoneView = this._isMobilePhoneView();

            for (var dataIndex = 0; dataIndex < data.length; dataIndex++) {
                var value = resourceValue(resource, data[dataIndex]);

                var tmp = new kendo.data.Query(events).filter({ field: resource.field, operator: ui.SchedulerView.groupEqFilter(value) }).toArray();

                if (tmp.length) {
                    var tasks = this._tasks(tmp);
                    var className = parent ? "" : " k-first";

                    if (dataIndex === data.length - 1 && (!parent || parent.className.indexOf("k-last") > -1)) {
                        className += " k-last";
                    }

                    var obj = {
                        text: kendo.getter(resource.dataTextField)(data[dataIndex]),
                        value: value,
                        rowSpan: 0,
                        className: className
                    };

                    if (resources.length > 1) {
                        obj.groups = this._createGroupConfiguration(tmp, resources.slice(1), obj);
                        if (parent) {
                            parent.rowSpan += obj.rowSpan;
                        }
                    } else {
                        obj.items = tasks;
                        var span = rowSpan(obj.items);

                        if (isPhoneView) {
                            span += obj.items.length;
                        }

                        obj.rowSpan = span;
                        if (parent) {
                            parent.rowSpan += span;
                        }
                    }
                    configuration.push(obj);
                }
            }

            return configuration;
        },

        selectionByElement: function (cell) {
            var index, event;
            cell = $(cell);
            if (cell.hasClass("k-scheduler-datecolumn")) {
                return;
            }

            if (this._isMobile()) {
                var parent = cell.parent();
                index = parent.parent().children()
                    .filter(function () {
                        return $(this).children(":not(.k-scheduler-datecolumn)").length;
                    })
                    .index(parent);
            } else {
                index = cell.parent().index();
            }

            event = this._eventsList[index];

            return {
                index: index,
                start: event.start,
                end: event.end,
                isAllDay: event.isAllDay,
                uid: event.uid
            };
        },

        select: function (selection) {
            this.clearSelection();

            var row = this.table
                .find(".k-task")
                .eq(selection.index)
                .closest("tr")
                .addClass("k-state-selected")
                .attr("aria-selected", true)[0];

            this.current(row);
        },

        move: function (selection, key) {
            var handled = false;
            var index = selection.index;

            if (key == kendo.keys.UP) {
                index--;
                handled = true;
            } else if (key == kendo.keys.DOWN) {
                index++;
                handled = true;
            }

            if (handled) {
                var event = this._eventsList[index];
                if (event) {
                    selection.start = event.start;
                    selection.end = event.end;
                    selection.isAllDay = event.isAllDay;
                    selection.events = [event.uid];
                    selection.index = index;
                }
            }

            return handled;
        },

        moveToEvent: function () {
            return false;
        },

        constrainSelection: function (selection) {
            var event = this._eventsList[0];
            if (event) {
                selection.start = event.start;
                selection.end = event.end;
                selection.isAllDay = event.isAllDay;
                selection.events = [event.uid];
                selection.index = 0;
            }
        },

        isInRange: function () {
            return true;
        },

        destroy: function () {
            if (this.element) {
                this.element.off(NS);
            }

            ui.SchedulerView.fn.destroy.call(this);
        },

        options: {
            title: "Agenda",
            name: "agenda",
            editable: true,
            selectedDateFormat: "{0:D}-{1:D}",
            eventTemplate: "#:title#",
            eventTimeTemplate: "#if(data.isAllDay) {#" +
                            '#=this.options.messages.allDay#' +
                          "#} else { #" +
                            '#=kendo.format(format, start, end)#' +
                          "# } #",
            eventDateTemplate: '<strong class="k-scheduler-agendaday">' +
                            '#=kendo.toString(date, "dd")#' +
                          '</strong>' +
                          '<em class="k-scheduler-agendaweek">' +
                              '#=kendo.toString(date,"dddd")#' +
                          '</em>' +
                          '<span class="k-scheduler-agendadate">' +
                              '#=kendo.toString(date, "y")#' +
                              '</span>',
            eventGroupTemplate: '<strong class="k-scheduler-adgendagroup">' +
                            '#=value#' +
                          '</strong>',
            messages: {
                event: "Event",
                date: "Date",
                time: "Time",
                allDay: "all day"
            }
        }
    });

    function rowSpan(tasks) {
        var result = 0;

        for (var idx = 0, length = tasks.length; idx < length; idx++) {
            result += tasks[idx].items.length;
        }

        return result;
    }

    function resourceValue(resource, item) {
        if (resource.valuePrimitive) {
            item = kendo.getter(resource.dataValueField)(item);
        }
        return item;
    }

    function flattenTaskGroups(groups) {
        var idx = 0,
            length = groups.length,
            item,
            result = [];

        for (; idx < length; idx++) {
            item = groups[idx];
            if (item.groups) {
                item = flattenGroup(item.groups);
                result = result.concat(item);
            } else {
                result = result.concat(flattenGroup(item.items));
            }
        }

        return result;
    }

    function flattenGroup(groups) {
        var items = [].concat(groups),
            item = items.shift(),
            result = [],
            push = [].push;
        while (item) {
            if (item.groups) {
                push.apply(items, item.groups);
            } else if (item.items) {
                push.apply(items, item.items);
            } else {
                push.call(result, item);
            }

            item = items.shift();
        }

        return result;
    }

})(window.kendo.jQuery);





(function ($) {
    var kendo = window.kendo,
        ui = kendo.ui,
        SchedulerView = ui.SchedulerView,
        NS = ".kendoMonthView",
        extend = $.extend,
        proxy = $.proxy,
        getDate = kendo.date.getDate,
        MS_PER_DAY = kendo.date.MS_PER_DAY,
        NUMBER_OF_ROWS = 6,
        NUMBER_OF_COLUMNS = 7,
        DAY_TEMPLATE = kendo.template('<span class="k-link k-nav-day">#:kendo.toString(date, "dd")#</span>'),
        EVENT_WRAPPER_STRING = '<div role="gridcell" aria-selected="false" data-#=ns#uid="#=uid#"' +
                '#if (resources[0]) { #' +
                    'style="background-color:#=resources[0].color #; border-color: #=resources[0].color#"' +
                    'class="k-event#=inverseColor ? " k-event-inverse" : ""#"' +
                '#} else {#' +
                    'class="k-event"' +
                '#}#' +
                '>' +
                '<span class="k-event-actions">' +
                    '# if(data.tail || data.middle) {#' +
                        '<span class="k-icon k-i-arrow-w"></span>' +
                    '#}#' +
                    '# if(data.isException()) {#' +
                        '<span class="k-icon k-i-exception"></span>' +
                    '# } else if(data.isRecurring()) {#' +
                        '<span class="k-icon k-i-refresh"></span>' +
                    '#}#' +
                '</span>' +
                '{0}' +
                '<span class="k-event-actions">' +
                    '#if (showDelete) {#' +
                        '<a href="\\#" class="k-link k-event-delete"><span class="k-icon k-si-close"></span></a>' +
                    '#}#' +
                    '# if(data.head || data.middle) {#' +
                        '<span class="k-icon k-i-arrow-e"></span>' +
                    '#}#' +
                '</span>' +
                '# if(resizable && !data.tail && !data.middle) {#' +
                '<span class="k-resize-handle k-resize-w"></span>' +
                '#}#' +
                '# if(resizable && !data.head && !data.middle) {#' +
                '<span class="k-resize-handle k-resize-e"></span>' +
                '#}#' +
                '</div>',
        EVENT_TEMPLATE = kendo.template('<div title="#=title.replace(/"/g,"&\\#34;")#">' +
                    '<div class="k-event-template">#:title#</div>' +
                '</div>');

    var MORE_BUTTON_TEMPLATE = kendo.template(
        '<div style="width:#=width#px;left:#=left#px;top:#=top#px" class="k-more-events k-button"><span>...</span></div>'
    );

    ui.MonthView = SchedulerView.extend({
        init: function (element, options) {
            var that = this;

            SchedulerView.fn.init.call(that, element, options);

            that.title = that.options.title;

            that.name = "month";

            that._templates();

            that._editable();

            that._renderLayout(that.options.date);

            that._groups();
        },

        _updateDirection: function (selection, ranges, multiple, reverse, vertical) {
            if (multiple) {
                var startSlot = ranges[0].start;
                var endSlot = ranges[ranges.length - 1].end;
                var isSameSlot = startSlot.index === endSlot.index;
                var isSameCollection = startSlot.collectionIndex === endSlot.collectionIndex;
                var updateDirection;

                if (vertical) {
                    updateDirection = (isSameSlot && isSameCollection) || isSameCollection;
                } else {
                    updateDirection = isSameSlot && isSameCollection;
                }

                if (updateDirection) {
                    selection.backward = reverse;
                }
            }
        },

        _changeViewPeriod: function (selection, reverse, vertical) {
            var pad = vertical ? 7 : 1;

            if (reverse) {
                pad *= -1;
            }

            selection.start = kendo.date.addDays(selection.start, pad);
            selection.end = kendo.date.addDays(selection.end, pad);

            if (!vertical || (vertical && this._isVerticallyGrouped())) {
                selection.groupIndex = reverse ? this.groups.length - 1 : 0;
            }

            selection.events = [];

            return true;
        },

        _continuousSlot: function (selection, ranges, reverse) {
            var index = selection.backward ? 0 : ranges.length - 1;
            var group = this.groups[selection.groupIndex];

            return group.continuousSlot(ranges[index].start, reverse);
        },

        _changeGroupContinuously: function (selection, continuousSlot, multiple, reverse) {
            if (!multiple) {
                var groupIndex = selection.groupIndex;
                var lastGroupIndex = this.groups.length - 1;
                var vertical = this._isVerticallyGrouped();
                var group = this.groups[groupIndex];

                if (!continuousSlot && vertical) {

                    continuousSlot = group[reverse ? "lastSlot" : "firstSlot"]();

                    groupIndex += (reverse ? -1 : 1);

                } else if (continuousSlot && !vertical) {
                    groupIndex = reverse ? lastGroupIndex : 0;
                }

                if (groupIndex < 0 || groupIndex > lastGroupIndex) {
                    groupIndex = reverse ? lastGroupIndex : 0;
                    continuousSlot = null;
                }

                selection.groupIndex = groupIndex;
            }

            return continuousSlot;
        },

        _normalizeHorizontalSelection: function (selection, ranges, reverse) {
            var slot;

            if (reverse) {
                slot = ranges[0].start;
            } else {
                slot = ranges[ranges.length - 1].end;
            }

            return slot;
        },

        _normalizeVerticalSelection: function (selection, ranges) {
            var slot;

            if (selection.backward) {
                slot = ranges[0].start;
            } else {
                slot = ranges[ranges.length - 1].end;
            }

            return slot;
        },

        _templates: function () {
            var options = this.options,
                settings = extend({}, kendo.Template, options.templateSettings);

            this.eventTemplate = this._eventTmpl(options.eventTemplate, EVENT_WRAPPER_STRING);
            this.dayTemplate = kendo.template(options.dayTemplate, settings);
            this.groupHeaderTemplate = kendo.template(options.groupHeaderTemplate, settings);
        },

        dateForTitle: function () {
            return kendo.format(this.options.selectedDateFormat, this._firstDayOfMonth, this._lastDayOfMonth);
        },

        nextDate: function () {
            return kendo.date.nextDay(this._lastDayOfMonth);
        },

        previousDate: function () {
            return kendo.date.previousDay(this._firstDayOfMonth);
        },

        startDate: function () {
            return this._startDate;
        },

        endDate: function () {
            return this._endDate;
        },

        _renderLayout: function (date) {
            var that = this;

            this._firstDayOfMonth = kendo.date.firstDayOfMonth(date);

            this._lastDayOfMonth = kendo.date.lastDayOfMonth(date);

            this._startDate = firstVisibleMonthDay(date, this.calendarInfo());

            this.createLayout(this._layout());

            this._content();

            this.refreshLayout();

            this.content.on("click" + NS, ".k-nav-day,.k-more-events", function (e) {
                var offset = $(e.currentTarget).offset();
                var slot = that._slotByPosition(offset.left, offset.top);

                e.preventDefault();
                that.trigger("navigate", { view: "day", date: slot.startDate() });
            });
        },

        _editable: function () {
            if (this.options.editable && !this._isMobilePhoneView()) {
                if (this._isMobile()) {
                    this._touchEditable();
                } else {
                    this._mouseEditable();
                }

            }
        },

        _mouseEditable: function () {
            var that = this;
            that.element.on("click" + NS, ".k-scheduler-monthview .k-event a:has(.k-si-close)", function (e) {
                that.trigger("remove", { uid: $(this).closest(".k-event").attr(kendo.attr("uid")) });
                e.preventDefault();
            });

            if (that.options.editable.create !== false) {
                that.element.on("dblclick" + NS, ".k-scheduler-monthview .k-scheduler-content td", function (e) {
                    var offset = $(e.currentTarget).offset();
                    var slot = that._slotByPosition(offset.left, offset.top);

                    if (slot) {
                        var resourceInfo = that._resourceBySlot(slot);
                        that.trigger("add", { eventInfo: extend({ isAllDay: true, start: slot.startDate(), end: slot.startDate() }, resourceInfo) });
                    }

                    e.preventDefault();
                });
            }

            if (that.options.editable.update !== false) {
                that.element.on("dblclick" + NS, ".k-scheduler-monthview .k-event", function (e) {
                    that.trigger("edit", { uid: $(this).closest(".k-event").attr(kendo.attr("uid")) });
                    e.preventDefault();
                });
            }
        },

        _touchEditable: function () {
            var that = this;

            if (that.options.editable.create !== false) {
                that._addUserEvents = new kendo.UserEvents(that.element, {
                    filter: ".k-scheduler-monthview .k-scheduler-content td",
                    tap: function (e) {
                        var offset = $(e.target).offset();
                        var slot = that._slotByPosition(offset.left, offset.top);

                        if (slot) {
                            var resourceInfo = that._resourceBySlot(slot);
                            that.trigger("add", { eventInfo: extend({ isAllDay: true, start: slot.startDate(), end: slot.startDate() }, resourceInfo) });
                        }

                        e.preventDefault();
                    }
                });
            }

            if (that.options.editable.update !== false) {
                that._editUserEvents = new kendo.UserEvents(that.element, {
                    filter: ".k-scheduler-monthview .k-event",
                    tap: function (e) {
                        if ($(e.event.target).closest("a:has(.k-si-close)").length === 0) {
                            that.trigger("edit", { uid: $(e.target).closest(".k-event").attr(kendo.attr("uid")) });
                            e.preventDefault();
                        }
                    }
                });
            }
        },

        selectionByElement: function (cell) {
            var offset = $(cell).offset();
            return this._slotByPosition(offset.left, offset.top);
        },

        _columnCountForLevel: function (level) {
            var columnLevel = this.columnLevels[level];
            return columnLevel ? columnLevel.length : 0;
        },

        _rowCountForLevel: function (level) {
            var rowLevel = this.rowLevels[level];
            return rowLevel ? rowLevel.length : 0;
        },

        _content: function () {
            var html = '<tbody>';
            var verticalGroupCount = 1;

            var resources = this.groupedResources;

            if (resources.length) {
                if (this._isVerticallyGrouped()) {
                    verticalGroupCount = this._rowCountForLevel(resources.length - 1);
                }
            }

            for (var verticalGroupIdx = 0; verticalGroupIdx < verticalGroupCount; verticalGroupIdx++) {
                html += this._createCalendar(verticalGroupIdx);
            }

            html += "</tbody>";

            this.content.find("table").html(html);
        },

        _createCalendar: function (verticalGroupIndex) {
            var start = this.startDate();
            var cellCount = NUMBER_OF_COLUMNS * NUMBER_OF_ROWS;
            var cellsPerRow = NUMBER_OF_COLUMNS;
            var weekStartDates = [start];
            var html = '';
            var horizontalGroupCount = 1;
            var isVerticallyGrouped = this._isVerticallyGrouped();

            var resources = this.groupedResources;

            if (resources.length) {
                if (!isVerticallyGrouped) {
                    horizontalGroupCount = this._columnCountForLevel(resources.length - 1);
                }
            }

            this._slotIndices = {};

            for (var rowIdx = 0, length = cellCount / cellsPerRow; rowIdx < length; rowIdx++) {
                html += "<tr>";

                weekStartDates.push(start);

                var startIdx = rowIdx * cellsPerRow;

                for (var groupIdx = 0; groupIdx < horizontalGroupCount; groupIdx++) {
                    html += this._createRow(start, startIdx, cellsPerRow, isVerticallyGrouped ? verticalGroupIndex : groupIdx);
                }

                start = kendo.date.addDays(start, cellsPerRow);

                html += "</tr>";
            }

            this._weekStartDates = weekStartDates;
            this._endDate = kendo.date.previousDay(start);

            return html;
        },

        _createRow: function (startDate, startIdx, cellsPerRow, groupIndex) {
            var that = this;
            var min = that._firstDayOfMonth;
            var max = that._lastDayOfMonth;
            var content = that.dayTemplate;
            var classes = "";
            var html = "";

            var resources = function () {
                return that._resourceBySlot({ groupIndex: groupIndex });
            };

            for (var cellIdx = 0; cellIdx < cellsPerRow; cellIdx++) {
                classes = "";

                if (kendo.date.isToday(startDate)) {
                    classes += "k-today";
                }

                if (!kendo.date.isInDateRange(startDate, min, max)) {
                    classes += " k-other-month";
                }

                html += "<td ";

                if (classes !== "") {
                    html += 'class="' + classes + '"';
                }

                html += ">";
                html += content({ date: startDate, resources: resources });
                html += "</td>";

                that._slotIndices[getDate(startDate).getTime()] = startIdx + cellIdx;

                startDate = kendo.date.nextDay(startDate);
            }

            return html;
        },

        _layout: function () {
            var calendarInfo = this.calendarInfo();
            var weekDayNames = this._isMobile() ? calendarInfo.days.namesShort : calendarInfo.days.names;
            var names = shiftArray(weekDayNames, calendarInfo.firstDay);
            var columns = $.map(names, function (value) { return { text: value }; });
            var resources = this.groupedResources;
            var rows;

            if (resources.length) {
                if (this._isVerticallyGrouped()) {
                    var inner = []; //add hidden cells in order to sync the content rows
                    for (var idx = 0; idx < 6; idx++) {
                        inner.push({ text: "<div>&nbsp;</div>", className: "k-hidden k-slot-cell" });
                    }

                    rows = this._createRowsLayout(resources, inner, this.groupHeaderTemplate);
                } else {
                    columns = this._createColumnsLayout(resources, columns, this.groupHeaderTemplate);
                }
            }

            return {
                columns: columns,
                rows: rows
            };
        },

        _createEventElement: function (event) {
            var options = this.options;
            var editable = options.editable;

            var isMobile = this._isMobile();

            event.showDelete = editable && editable.destroy !== false && !isMobile;
            event.resizable = editable && editable.resize !== false && !isMobile;
            event.ns = kendo.ns;
            event.resources = this.eventResources(event);
            event.inverseColor = event.resources && event.resources[0] ? this._shouldInverseResourceColor(event.resources[0]) : false;

            var element = $(this.eventTemplate(event));

            this.angular("compile", function () {
                return {
                    elements: element,
                    data: [{ dataItem: event}]
                };
            });

            return element;
        },
        _isInDateSlot: function (event) {
            var groups = this.groups[0];
            var slotStart = groups.firstSlot().start;
            var slotEnd = groups.lastSlot().end - 1;

            var startTime = kendo.date.toUtcTime(event.start);
            var endTime = kendo.date.toUtcTime(event.end);

            return (isInDateRange(startTime, slotStart, slotEnd) ||
                isInDateRange(endTime, slotStart, slotEnd) ||
                isInDateRange(slotStart, startTime, endTime) ||
                isInDateRange(slotEnd, startTime, endTime)) &&
                (!isInDateRange(endTime, slotStart, slotStart) || isInDateRange(endTime, startTime, startTime) || event.isAllDay);
        },

        _slotIndex: function (date) {
            return this._slotIndices[getDate(date).getTime()];
        },

        _positionMobileEvent: function (slotRange, element, group) {
            var startSlot = slotRange.start;

            if (slotRange.start.offsetLeft > slotRange.end.offsetLeft) {
                startSlot = slotRange.end;
            }

            var startIndex = slotRange.start.index;
            var endIndex = startIndex;

            var eventCount = 3;
            var events = SchedulerView.collidingEvents(slotRange.events(), startIndex, endIndex);

            events.push({ element: element, start: startIndex, end: endIndex });

            var rows = SchedulerView.createRows(events);

            var slot = slotRange.collection.at(startIndex);

            var container = slot.container;

            if (!container) {

                container = $(kendo.format('<div class="k-events-container" style="top:{0};left:{1};width:{2}"/>',
                    startSlot.offsetTop + startSlot.firstChildTop + startSlot.firstChildHeight - 3 + "px",
                    startSlot.offsetLeft + "px",
                    startSlot.offsetWidth + "px"
                ));

                slot.container = container;

                this.content[0].appendChild(container[0]);
            }

            if (rows.length <= eventCount) {
                slotRange.addEvent({ element: element, start: startIndex, end: endIndex, groupIndex: startSlot.groupIndex });

                group._continuousEvents.push({
                    element: element,
                    uid: element.attr(kendo.attr("uid")),
                    start: slotRange.start,
                    end: slotRange.end
                });

                container[0].appendChild(element[0]);
            }
        },

        _positionEvent: function (slotRange, element, group) {
            var eventHeight = this.options.eventHeight;
            var startSlot = slotRange.start;

            if (slotRange.start.offsetLeft > slotRange.end.offsetLeft) {
                startSlot = slotRange.end;
            }

            var startIndex = slotRange.start.index;
            var endIndex = slotRange.end.index;
            var eventCount = startSlot.eventCount;
            var events = SchedulerView.collidingEvents(slotRange.events(), startIndex, endIndex);
            var rightOffset = startIndex !== endIndex ? 5 : 4;

            events.push({ element: element, start: startIndex, end: endIndex });

            var rows = SchedulerView.createRows(events);

            for (var idx = 0, length = Math.min(rows.length, eventCount); idx < length; idx++) {
                var rowEvents = rows[idx].events;
                var eventTop = startSlot.offsetTop + startSlot.firstChildHeight + idx * eventHeight + 3 * idx + "px";

                for (var j = 0, eventLength = rowEvents.length; j < eventLength; j++) {
                    rowEvents[j].element[0].style.top = eventTop;
                }
            }

            if (rows.length > eventCount) {
                for (var slotIndex = startIndex; slotIndex <= endIndex; slotIndex++) {
                    var collection = slotRange.collection;

                    var slot = collection.at(slotIndex);

                    if (slot.more) {
                        return;
                    }

                    slot.more = $(MORE_BUTTON_TEMPLATE({
                        ns: kendo.ns,
                        start: slotIndex,
                        end: slotIndex,
                        width: slot.clientWidth - 2,
                        left: slot.offsetLeft + 2,
                        top: slot.offsetTop + slot.firstChildHeight + eventCount * eventHeight + 3 * eventCount
                    }));

                    this.content[0].appendChild(slot.more[0]);
                }
            } else {
                slotRange.addEvent({ element: element, start: startIndex, end: endIndex, groupIndex: startSlot.groupIndex });

                element[0].style.width = slotRange.innerWidth() - rightOffset + "px";
                element[0].style.left = startSlot.offsetLeft + 2 + "px";
                element[0].style.height = eventHeight + "px";

                group._continuousEvents.push({
                    element: element,
                    uid: element.attr(kendo.attr("uid")),
                    start: slotRange.start,
                    end: slotRange.end
                });

                this.content[0].appendChild(element[0]);
            }
        },

        _slotByPosition: function (x, y) {
            var offset = this.content.offset();

            x -= offset.left;
            y -= offset.top;
            y += this.content[0].scrollTop;
            x += this.content[0].scrollLeft;

            x = Math.ceil(x);
            y = Math.ceil(y);

            for (var groupIndex = 0; groupIndex < this.groups.length; groupIndex++) {
                var slot = this.groups[groupIndex].daySlotByPosition(x, y);

                if (slot) {
                    return slot;
                }
            }

            return null;
        },

        _createResizeHint: function (range) {
            var left = range.startSlot().offsetLeft;

            var top = range.start.offsetTop;

            var width = range.innerWidth();

            var height = range.start.clientHeight - 2;

            var hint = SchedulerView.fn._createResizeHint.call(this, left, top, width, height);

            hint.appendTo(this.content);

            this._resizeHint = this._resizeHint.add(hint);
        },

        _updateResizeHint: function (event, groupIndex, startTime, endTime) {
            this._removeResizeHint();

            var group = this.groups[groupIndex];

            var ranges = group.ranges(startTime, endTime, true, event.isAllDay);

            for (var rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) {
                this._createResizeHint(ranges[rangeIndex]);
            }

            this._resizeHint.find(".k-label-top,.k-label-bottom").text("");

            this._resizeHint.first().addClass("k-first").find(".k-label-top").text(kendo.toString(kendo.timezone.toLocalDate(startTime), "M/dd"));

            this._resizeHint.last().addClass("k-last").find(".k-label-bottom").text(kendo.toString(kendo.timezone.toLocalDate(endTime), "M/dd"));
        },

        _updateMoveHint: function (event, groupIndex, distance) {
            var start = kendo.date.toUtcTime(event.start) + distance;

            var end = start + event.duration();

            var group = this.groups[groupIndex];

            var ranges = group.ranges(start, end, true, event.isAllDay);

            this._removeMoveHint();

            for (var rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) {
                var range = ranges[rangeIndex];

                var startSlot = range.startSlot();

                var endSlot = range.endSlot();

                var hint = this._createEventElement(event.clone({ head: range.head, tail: range.tail }));

                hint.css({
                    left: startSlot.offsetLeft + 2,
                    top: startSlot.offsetTop + startSlot.firstChildHeight,
                    height: this.options.eventHeight,
                    width: range.innerWidth() - (startSlot.index !== endSlot.index ? 5 : 4)
                });

                hint.addClass("k-event-drag-hint");

                hint.appendTo(this.content);
                this._moveHint = this._moveHint.add(hint);
            }
        },

        _groups: function () {
            var groupCount = this._groupCount();
            var columnCount = NUMBER_OF_COLUMNS;
            var rowCount = NUMBER_OF_ROWS;

            this.groups = [];

            for (var idx = 0; idx < groupCount; idx++) {
                this._addResourceView(idx);
            }

            var tableRows = this.content[0].getElementsByTagName("tr");

            for (var groupIndex = 0; groupIndex < groupCount; groupIndex++) {
                var cellCount = 0;
                var rowMultiplier = 0;

                if (this._isVerticallyGrouped()) {
                    rowMultiplier = groupIndex;
                }

                for (var rowIndex = rowMultiplier * rowCount; rowIndex < (rowMultiplier + 1) * rowCount; rowIndex++) {
                    var group = this.groups[groupIndex];
                    var collection = group.addDaySlotCollection(kendo.date.addDays(this.startDate(), cellCount), kendo.date.addDays(this.startDate(), cellCount + columnCount));

                    var tableRow = tableRows[rowIndex];
                    var cells = tableRow.children;
                    var cellMultiplier = 0;

                    tableRow.setAttribute("role", "row");

                    if (!this._isVerticallyGrouped()) {
                        cellMultiplier = groupIndex;
                    }

                    for (var cellIndex = cellMultiplier * columnCount; cellIndex < (cellMultiplier + 1) * columnCount; cellIndex++) {
                        var cell = cells[cellIndex];

                        var clientHeight = cell.clientHeight;

                        var firstChildHeight = cell.children.length ? cell.children[0].offsetHeight + 3 : 0;

                        var start = kendo.date.toUtcTime(kendo.date.addDays(this.startDate(), cellCount));

                        cellCount++;

                        var eventCount = Math.floor((clientHeight - firstChildHeight - this.options.moreButtonHeight) / (this.options.eventHeight + 3)); // add space for the more button

                        cell.setAttribute("role", "gridcell");
                        cell.setAttribute("aria-selected", false);

                        collection.addDaySlot(cell, start, start + kendo.date.MS_PER_DAY, eventCount);
                    }
                }
            }
        },

        render: function (events) {
            this.content.children(".k-event,.k-more-events,.k-events-container").remove();

            this._groups();

            events = new kendo.data.Query(events).sort([{ field: "start", dir: "asc" }, { field: "end", dir: "desc"}]).toArray();

            var resources = this.groupedResources;
            if (resources.length) {
                this._renderGroups(events, resources, 0, 1);
            } else {
                this._renderEvents(events, 0);
            }

            this.refreshLayout();
            this.trigger("activate");
        },

        _renderEvents: function (events, groupIndex) {
            var event;
            var idx;
            var length;
            var isMobilePhoneView = this._isMobilePhoneView();

            for (idx = 0, length = events.length; idx < length; idx++) {
                event = events[idx];

                if (this._isInDateSlot(event)) {
                    var group = this.groups[groupIndex];

                    if (!group._continuousEvents) {
                        group._continuousEvents = [];
                    }

                    var ranges = group.slotRanges(event, true);

                    var rangeCount = ranges.length;

                    for (var rangeIndex = 0; rangeIndex < rangeCount; rangeIndex++) {
                        var range = ranges[rangeIndex];
                        var start = event.start;
                        var end = event.end;

                        if (rangeCount > 1) {
                            if (rangeIndex === 0) {
                                end = range.end.endDate();
                            } else if (rangeIndex == rangeCount - 1) {
                                start = range.start.startDate();
                            } else {
                                start = range.start.startDate();
                                end = range.end.endDate();
                            }
                        }

                        var occurrence = event.clone({ start: start, end: end, head: range.head, tail: range.tail });

                        if (isMobilePhoneView) {
                            this._positionMobileEvent(range, this._createEventElement(occurrence), group);
                        } else {
                            this._positionEvent(range, this._createEventElement(occurrence), group);
                        }
                    }
                }
            }
        },

        _renderGroups: function (events, resources, offset, columnLevel) {
            var resource = resources[0];

            if (resource) {
                var view = resource.dataSource.view();

                for (var itemIdx = 0; itemIdx < view.length; itemIdx++) {
                    var value = this._resourceValue(resource, view[itemIdx]);

                    var tmp = new kendo.data.Query(events).filter({ field: resource.field, operator: SchedulerView.groupEqFilter(value) }).toArray();

                    if (resources.length > 1) {
                        offset = this._renderGroups(tmp, resources.slice(1), offset++, columnLevel + 1);
                    } else {
                        this._renderEvents(tmp, offset++);
                    }
                }
            }
            return offset;
        },

        _groupCount: function () {
            var resources = this.groupedResources;

            if (resources.length) {
                if (this._isVerticallyGrouped()) {
                    return this._rowCountForLevel(resources.length - 1);
                } else {
                    return this._columnCountForLevel(resources.length) / this._columnOffsetForResource(resources.length);
                }
            }
            return 1;
        },

        _columnOffsetForResource: function (index) {
            return this._columnCountForLevel(index) / this._columnCountForLevel(index - 1);
        },

        destroy: function () {
            if (this.table) {
                this.table.removeClass("k-scheduler-monthview");
            }

            if (this.content) {
                this.content.off(NS);
            }

            if (this.element) {
                this.element.off(NS);
            }

            SchedulerView.fn.destroy.call(this);

            if (this._isMobile() && !this._isMobilePhoneView() && this.options.editable) {
                if (this.options.editable.create !== false) {
                    this._addUserEvents.destroy();
                }

                if (this.options.editable.update !== false) {
                    this._editUserEvents.destroy();
                }
            }
        },

        events: ["remove", "add", "edit", "navigate"],

        options: {
            title: "Month",
            name: "month",
            eventHeight: 25,
            moreButtonHeight: 13,
            editable: true,
            selectedDateFormat: "{0:y}",
            groupHeaderTemplate: "#=text#",
            dayTemplate: DAY_TEMPLATE,
            eventTemplate: EVENT_TEMPLATE
        }
    });


    function shiftArray(array, idx) {
        return array.slice(idx).concat(array.slice(0, idx));
    }

    function firstVisibleMonthDay(date, calendarInfo) {
        var firstDay = calendarInfo.firstDay,
            firstVisibleDay = new Date(date.getFullYear(), date.getMonth(), 0, date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());

        while (firstVisibleDay.getDay() != firstDay) {
            kendo.date.setTime(firstVisibleDay, -1 * MS_PER_DAY);
        }

        return firstVisibleDay;
    }

    function isInDateRange(value, min, max) {
        var msMin = min,
            msMax = max,
            msValue;

        msValue = value;

        return msValue >= msMin && msValue <= msMax;
    }
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        timezone = kendo.timezone,
        Class = kendo.Class,
        ui = kendo.ui,
        Widget = ui.Widget,
        DropDownList = ui.DropDownList,
        kendoDate = kendo.date,
        setTime = kendoDate.setTime,
        setDayOfWeek = kendoDate.setDayOfWeek,
        adjustDST = kendoDate.adjustDST,
        firstDayOfMonth = kendoDate.firstDayOfMonth,
        getMilliseconds = kendoDate.getMilliseconds,
        DAYS_IN_LEAPYEAR = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366],
        DAYS_IN_YEAR = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365],
        MONTHS = [31, 28, 30, 31, 30, 31, 30, 31, 30, 31, 30, 31],
        WEEK_DAYS = {
            0: "SU",
            1: "MO",
            2: "TU",
            3: "WE",
            4: "TH",
            5: "FR",
            6: "SA"
        },
        WEEK_DAYS_IDX = {
            "SU": 0,
            "MO": 1,
            "TU": 2,
            "WE": 3,
            "TH": 4,
            "FR": 5,
            "SA": 6
        },
        DATE_FORMATS = [
            "yyyy-MM-ddTHH:mm:ss.fffzzz",
            "yyyy-MM-ddTHH:mm:sszzz",
            "yyyy-MM-ddTHH:mm:ss",
            "yyyy-MM-ddTHH:mm",
            "yyyy-MM-ddTHH",
            "yyyy-MM-dd",
            "yyyyMMddTHHmmssfffzzz",
            "yyyyMMddTHHmmsszzz",
            "yyyyMMddTHHmmss",
            "yyyyMMddTHHmm",
            "yyyyMMddTHH",
            "yyyyMMdd"
        ],
        RULE_NAMES = ["months", "weeks", "yearDays", "monthDays", "weekDays", "hours", "minutes", "seconds"],
        RULE_NAMES_LENGTH = RULE_NAMES.length,
        RECURRENCE_DATE_FORMAT = "yyyyMMddTHHmmssZ",
        limitation = {
            months: function (date, end, rule) {
                var monthRules = rule.months,
                    months = ruleValues(monthRules, date.getMonth() + 1),
                    changed = false;

                if (months !== null) {
                    if (months.length) {
                        date.setMonth(months[0] - 1, 1);
                    } else {
                        date.setFullYear(date.getFullYear() + 1, monthRules[0] - 1, 1);
                    }

                    changed = true;
                }

                return changed;
            },

            monthDays: function (date, end, rule) {
                var monthLength, month, days,
                    changed = false,
                    hours = date.getHours(),
                    normalize = function (monthDay) {
                        if (monthDay < 0) {
                            monthDay = monthLength + monthDay;
                        }
                        return monthDay;
                    };

                while (date <= end) {
                    month = date.getMonth();
                    monthLength = getMonthLength(date);
                    days = ruleValues(rule.monthDays, date.getDate(), normalize);

                    if (days === null) {
                        return changed;
                    }

                    changed = true;

                    if (days.length) {
                        date.setMonth(month, days.sort(numberSortPredicate)[0]);
                        adjustDST(date, hours);

                        if (month === date.getMonth()) {
                            break;
                        }
                    } else {
                        date.setMonth(month + 1, 1);
                    }
                }

                return changed;
            },

            yearDays: function (date, end, rule) {
                var year, yearDays,
                    changed = false,
                    hours = date.getHours(),
                    normalize = function (yearDay) {
                        if (yearDay < 0) {
                            yearDay = year + yearDay;
                        }
                        return yearDay;
                    };

                while (date < end) {
                    year = leapYear(date) ? 366 : 365;
                    yearDays = ruleValues(rule.yearDays, dayInYear(date), normalize);

                    if (yearDays === null) {
                        return changed;
                    }

                    changed = true;
                    year = date.getFullYear();

                    if (yearDays.length) {
                        date.setFullYear(year, 0, yearDays.sort(numberSortPredicate)[0]);
                        adjustDST(date, hours);
                        break;
                    } else {
                        date.setFullYear(year + 1, 0, 1);
                    }
                }

                return changed;
            },

            weeks: function (date, end, rule) {
                var weekStart = rule.weekStart,
                    year, weeks, day,
                    changed = false,
                    hours = date.getHours(),
                    normalize = function (week) {
                        if (week < 0) {
                            week = 53 + week;
                        }
                        return week;
                    };

                while (date < end) {
                    weeks = ruleValues(rule.weeks, weekInYear(date, weekStart), normalize);

                    if (weeks === null) {
                        return changed;
                    }

                    changed = true;
                    year = date.getFullYear();

                    if (weeks.length) {
                        day = (weeks.sort(numberSortPredicate)[0] * 7) - 1;

                        date.setFullYear(year, 0, day);
                        setDayOfWeek(date, weekStart, -1);

                        adjustDST(date, hours);
                        break;
                    } else {
                        date.setFullYear(year + 1, 0, 1);
                    }
                }

                return changed;
            },

            weekDays: function (date, end, rule) {
                var weekDays = rule.weekDays;
                var weekStart = rule.weekStart;
                var weekDayRules = ruleWeekValues(weekDays, date, weekStart);
                var hours = date.getHours();
                var weekDayRule, day;

                if (weekDayRules === null) {
                    return false;
                }

                weekDayRule = weekDayRules[0];
                if (!weekDayRule) {
                    weekDayRule = weekDays[0];
                    setDayOfWeek(date, weekStart);
                }

                day = weekDayRule.day;

                if (weekDayRule.offset) {
                    while (date <= end && !isInWeek(date, weekDayRule, weekStart)) {
                        if (weekInMonth(date, weekStart) === numberOfWeeks(date, weekStart)) {
                            date.setMonth(date.getMonth() + 1, 1);
                            adjustDST(date, hours);
                        } else {
                            date.setDate(date.getDate() + 7);
                            adjustDST(date, hours);

                            setDayOfWeek(date, weekStart, -1);
                        }
                    }
                }

                if (date.getDay() !== day) {
                    setDayOfWeek(date, day);
                }

                return true;
            },

            hours: function (date, end, rule) {
                var hourRules = rule.hours,
                    startTime = rule._startTime,
                    startHours = startTime.getHours(),
                    hours = ruleValues(hourRules, startHours),
                    changed = false;

                if (hours !== null) {
                    changed = true;

                    date.setHours(startHours);
                    adjustDST(date, startHours);

                    if (hours.length) {
                        hours = hours[0];
                        date.setHours(hours);
                    } else {
                        hours = date.getHours();
                        date.setDate(date.getDate() + 1);
                        adjustDST(date, hours);

                        hours = hourRules[0];
                        date.setHours(hours);
                        adjustDST(date, hours);
                    }

                    if (rule.minutes) {
                        date.setMinutes(0);
                    }

                    startTime.setHours(hours, date.getMinutes());
                }

                return changed;
            },

            minutes: function (date, end, rule) {
                var minuteRules = rule.minutes,
                    currentMinutes = date.getMinutes(),
                    minutes = ruleValues(minuteRules, currentMinutes),
                    hours = rule._startTime.getHours(),
                    changed = false;

                if (minutes !== null) {
                    changed = true;

                    if (minutes.length) {
                        minutes = minutes[0];
                    } else {
                        hours += 1;
                        minutes = minuteRules[0];
                    }

                    if (rule.seconds) {
                        date.setSeconds(0);
                    }

                    date.setHours(hours, minutes);

                    hours = hours % 24;
                    adjustDST(date, hours);
                    rule._startTime.setHours(hours, minutes, date.getSeconds());
                }

                return changed;
            },

            seconds: function (date, end, rule) {
                var secondRules = rule.seconds,
                    hours = rule._startTime.getHours(),
                    seconds = ruleValues(secondRules, date.getSeconds()),
                    minutes = date.getMinutes(),
                    changed = false;

                if (seconds !== null) {
                    changed = true;

                    if (seconds.length) {
                        date.setSeconds(seconds[0]);
                    } else {
                        minutes += 1;
                        date.setMinutes(minutes, secondRules[0]);

                        if (minutes > 59) {
                            minutes = minutes % 60;
                            hours = (hours + 1) % 24;
                        }
                    }

                    rule._startTime.setHours(hours, minutes, date.getSeconds());
                }

                return changed;
            }
        },
        BaseFrequency = Class.extend({
            next: function (date, rule) {
                var startTime = rule._startTime,
                    day = startTime.getDate(),
                    minutes, seconds;

                if (rule.seconds) {
                    seconds = date.getSeconds() + 1;

                    date.setSeconds(seconds);
                    startTime.setSeconds(seconds);
                    startTime.setDate(day);

                } else if (rule.minutes) {
                    minutes = date.getMinutes() + 1;

                    date.setMinutes(minutes);
                    startTime.setMinutes(minutes);
                    startTime.setDate(day);
                } else {
                    return false;
                }

                return true;
            },

            normalize: function (options) {
                var rule = options.rule;

                if (options.idx === 4 && rule.hours) {
                    rule._startTime.setHours(0);
                    this._hour(options.date, rule);
                }
            },

            limit: function (date, end, rule) {
                var interval = rule.interval,
                    ruleName, firstRule,
                    modified,
                    idx, day;

                while (date <= end) {
                    modified = firstRule = undefined;
                    day = date.getDate();

                    for (idx = 0; idx < RULE_NAMES_LENGTH; idx++) {
                        ruleName = RULE_NAMES[idx];

                        if (rule[ruleName]) {
                            modified = limitation[ruleName](date, end, rule);
                            if (firstRule !== undefined && modified) {
                                break;
                            } else {
                                firstRule = modified;
                            }
                        }

                        if (modified) {
                            this.normalize({ date: date, rule: rule, day: day, idx: idx });
                        }
                    }

                    if ((interval === 1 || !this.interval(rule, date)) && idx === RULE_NAMES_LENGTH) {
                        break;
                    }
                }
            },

            interval: function (rule, current) {
                var start = new Date(rule._startPeriod);
                var date = new Date(current);
                var hours = current.getHours();
                var weekStart = rule.weekStart;
                var interval = rule.interval;
                var frequency = rule.freq;
                var modified = false;
                var excess = 0;
                var month = 0;
                var day = 1;
                var diff;

                var startTimeHours;

                if (frequency === "hourly") {
                    diff = date.getTimezoneOffset() - start.getTimezoneOffset();
                    startTimeHours = rule._startTime.getHours();

                    date = date.getTime();
                    if (hours !== startTimeHours) {
                        date += (startTimeHours - hours) * kendoDate.MS_PER_HOUR;
                    }
                    date -= start;

                    if (diff) {
                        date -= diff * kendoDate.MS_PER_MINUTE;
                    }

                    diff = Math.floor(date / kendoDate.MS_PER_HOUR);
                    excess = intervalExcess(diff, interval);

                    if (excess !== 0) {
                        this._hour(current, rule, excess);
                        modified = true;
                    }
                } else if (frequency === "daily") {
                    kendoDate.setTime(date, -start);

                    diff = Math.floor(date / kendoDate.MS_PER_DAY);
                    excess = intervalExcess(diff, interval);

                    if (excess !== 0) {
                        this._date(current, rule, excess);
                        modified = true;
                    }

                } else if (frequency === "weekly") {
                    diff = (current.getFullYear() - start.getFullYear()) * 52;

                    excess = weekInYear(current, weekStart) - weekInYear(start, weekStart) + diff;
                    excess = intervalExcess(excess, interval);

                    if (excess !== 0) {
                        kendoDate.setDayOfWeek(current, rule.weekStart, -1);

                        current.setDate(current.getDate() + (excess * 7));
                        adjustDST(current, hours);

                        modified = true;
                    }
                } else if (frequency === "monthly") {
                    diff = current.getFullYear() - start.getFullYear();
                    diff = current.getMonth() - start.getMonth() + (diff * 12);

                    excess = intervalExcess(diff, interval);

                    if (excess !== 0) {
                        day = rule._hasRuleValue ? 1 : current.getDate();

                        current.setFullYear(current.getFullYear(), current.getMonth() + excess, day);
                        adjustDST(current, hours);

                        modified = true;
                    }
                } else if (frequency === "yearly") {
                    diff = current.getFullYear() - start.getFullYear();
                    excess = intervalExcess(diff, interval);

                    if (!rule.months) {
                        month = current.getMonth();
                    }

                    if (!rule.yearDays && !rule.monthDays && !rule.weekDays) {
                        day = current.getDate();
                    }

                    if (excess !== 0) {
                        current.setFullYear(current.getFullYear() + excess, month, day);
                        adjustDST(current, hours);

                        modified = true;
                    }
                }

                return modified;
            },

            _hour: function (date, rule, interval) {
                var startTime = rule._startTime,
                    hours = startTime.getHours();

                if (interval) {
                    hours += interval;
                }

                date.setHours(hours);

                hours = hours % 24;
                startTime.setHours(hours);
                adjustDST(date, hours);
            },

            _date: function (date, rule, interval) {
                var hours = date.getHours();

                date.setDate(date.getDate() + interval);
                if (!adjustDST(date, hours)) {
                    this._hour(date, rule);
                }
            }
        }),
        HourlyFrequency = BaseFrequency.extend({
            next: function (date, rule) {
                if (!BaseFrequency.fn.next(date, rule)) {
                    this._hour(date, rule, 1);
                }
            },

            normalize: function (options) {
                var rule = options.rule;

                if (options.idx === 4) {
                    rule._startTime.setHours(0);
                    this._hour(options.date, rule);
                }
            }
        }),
        DailyFrequency = BaseFrequency.extend({
            next: function (date, rule) {
                if (!BaseFrequency.fn.next(date, rule)) {
                    this[rule.hours ? "_hour" : "_date"](date, rule, 1);
                }
            }
        }),
        WeeklyFrequency = DailyFrequency.extend({
            setup: function (rule, eventStartDate) {
                if (!rule.weekDays) {
                    rule.weekDays = [{
                        day: eventStartDate.getDay(),
                        offset: 0
                    }];
                }
            }
        }),
        MonthlyFrequency = BaseFrequency.extend({
            next: function (date, rule) {
                var day, hours;
                if (!BaseFrequency.fn.next(date, rule)) {
                    if (rule.hours) {
                        this._hour(date, rule, 1);
                    } else if (rule.monthDays || rule.weekDays || rule.yearDays || rule.weeks) {
                        this._date(date, rule, 1);
                    } else {
                        day = date.getDate();
                        hours = date.getHours();

                        date.setMonth(date.getMonth() + 1);
                        adjustDST(date, hours);

                        while (date.getDate() !== day) {
                            date.setDate(day);
                            adjustDST(date, hours);
                        }

                        this._hour(date, rule);
                    }
                }
            },
            normalize: function (options) {
                var rule = options.rule,
                    date = options.date,
                    hours = date.getHours();

                if (options.idx === 0 && !rule.monthDays && !rule.weekDays) {
                    date.setDate(options.day);
                    adjustDST(date, hours);
                } else {
                    BaseFrequency.fn.normalize(options);
                }
            },
            setup: function (rule, eventStartDate, date) {
                if (!rule.monthDays && !rule.weekDays) {
                    date.setDate(eventStartDate.getDate());
                }
            }
        }),
        YearlyFrequency = MonthlyFrequency.extend({
            next: function (date, rule) {
                var day,
                    hours = date.getHours();

                if (!BaseFrequency.fn.next(date, rule)) {
                    if (rule.hours) {
                        this._hour(date, rule, 1);
                    } else if (rule.monthDays || rule.weekDays || rule.yearDays || rule.weeks) {
                        this._date(date, rule, 1);
                    } else if (rule.months) {
                        day = date.getDate();

                        date.setMonth(date.getMonth() + 1);
                        adjustDST(date, hours);

                        while (date.getDate() !== day) {
                            date.setDate(day);
                            adjustDST(date, hours);
                        }

                        this._hour(date, rule);
                    } else {
                        date.setFullYear(date.getFullYear() + 1);
                        adjustDST(date, hours);

                        this._hour(date, rule);
                    }
                }
            },
            setup: function () { }
        }),
        frequencies = {
            "hourly": new HourlyFrequency(),
            "daily": new DailyFrequency(),
            "weekly": new WeeklyFrequency(),
            "monthly": new MonthlyFrequency(),
            "yearly": new YearlyFrequency()
        },
        CLICK = "click";

    function intervalExcess(diff, interval) {
        var excess;
        if (diff !== 0 && diff < interval) {
            excess = interval - diff;
        } else {
            excess = diff % interval;
            if (excess) {
                excess = interval - excess;
            }
        }

        return excess;
    }

    function dayInYear(date) {
        var month = date.getMonth();
        var days = leapYear(date) ? DAYS_IN_LEAPYEAR[month] : DAYS_IN_YEAR[month];

        return days + date.getDate();
    }

    function weekInYear(date, weekStart) {
        var year, days;

        date = new Date(date.getFullYear(), date.getMonth(), date.getDate());
        adjustDST(date, 0);

        year = date.getFullYear();

        if (weekStart !== undefined) {
            setDayOfWeek(date, weekStart, -1);
            date.setDate(date.getDate() + 4);
        } else {
            date.setDate(date.getDate() + (4 - (date.getDay() || 7)));
        }

        adjustDST(date, 0);
        days = Math.floor((date.getTime() - new Date(year, 0, 1, -6)) / 86400000);

        return 1 + Math.floor(days / 7);
    }

    function weekInMonth(date, weekStart) {
        var firstWeekDay = firstDayOfMonth(date).getDay();
        var firstWeekLength = 7 - (firstWeekDay + 7 - (weekStart || 7)) || 7;

        if (firstWeekLength < 0) {
            firstWeekLength += 7;
        }

        return Math.ceil((date.getDate() - firstWeekLength) / 7) + 1;
    }

    function normalizeDayIndex(weekDay, weekStart) {
        return weekDay + (weekDay < weekStart ? 7 : 0);
    }

    function normalizeOffset(date, rule, weekStart) {
        var offset = rule.offset;

        if (!offset) {
            return weekInMonth(date, weekStart);
        }

        var lastDate = new Date(date.getFullYear(), date.getMonth() + 1, 0);
        var weeksInMonth = weekInMonth(lastDate, weekStart);

        var day = normalizeDayIndex(rule.day, weekStart);

        var skipFirst = day < normalizeDayIndex(new Date(date.getFullYear(), date.getMonth(), 1).getDay(), weekStart);
        var skipLast = day > normalizeDayIndex(lastDate.getDay(), weekStart);

        if (offset < 0) {
            offset = weeksInMonth + (offset + 1 - (skipLast ? 1 : 0));
        } else if (skipFirst) {
            offset += 1;
        }

        weeksInMonth -= (skipLast ? 1 : 0);

        if (offset < (skipFirst ? 1 : 0) || offset > weeksInMonth) {
            return null;
        }

        return offset;
    }

    function numberOfWeeks(date, weekStart) {
        return weekInMonth(new Date(date.getFullYear(), date.getMonth() + 1, 0), weekStart);
    }

    function isInWeek(date, rule, weekStart) {
        return weekInMonth(date, weekStart) === normalizeOffset(date, rule, weekStart);
    }

    function ruleWeekValues(weekDays, date, weekStart) {
        var currentDay = normalizeDayIndex(date.getDay(), weekStart);
        var length = weekDays.length;
        var ruleWeekOffset;
        var weekDay, day;
        var weekNumber;
        var result = [];
        var idx = 0;

        for (; idx < length; idx++) {
            weekDay = weekDays[idx];

            weekNumber = weekInMonth(date, weekStart);
            ruleWeekOffset = normalizeOffset(date, weekDay, weekStart);

            if (ruleWeekOffset === null) {
                continue;
            }

            if (weekNumber < ruleWeekOffset) {
                result.push(weekDay);
            } else if (weekNumber === ruleWeekOffset) {
                day = normalizeDayIndex(weekDay.day, weekStart);

                if (currentDay < day) {
                    result.push(weekDay);
                } else if (currentDay === day) {
                    return null;
                }
            }
        }

        return result;
    }

    function ruleValues(rules, value, normalize) {
        var idx = 0,
            length = rules.length,
            availableRules = [],
            ruleValue;

        for (; idx < length; idx++) {
            ruleValue = rules[idx];

            if (normalize) {
                ruleValue = normalize(ruleValue);
            }

            if (value === ruleValue) {
                return null;
            } else if (value < ruleValue) {
                availableRules.push(ruleValue);
            }
        }

        return availableRules;
    }

    function parseArray(list, range) {
        var idx = 0,
            length = list.length,
            value;

        for (; idx < length; idx++) {
            value = parseInt(list[idx], 10);
            if (isNaN(value) || value < range.start || value > range.end || (value === 0 && range.start < 0)) {
                return null;
            }

            list[idx] = value;
        }

        return list.sort(numberSortPredicate);
    }

    function parseWeekDayList(list) {
        var idx = 0, length = list.length,
            value, valueLength, day;

        for (; idx < length; idx++) {
            value = list[idx];
            valueLength = value.length;
            day = value.substring(valueLength - 2).toUpperCase();

            day = WEEK_DAYS_IDX[day];
            if (day === undefined) {
                return null;
            }

            list[idx] = {
                offset: parseInt(value.substring(0, valueLength - 2), 10) || 0,
                day: day
            };
        }
        return list;
    }

    function serializeWeekDayList(list) {
        var idx = 0, length = list.length,
            value, valueString, result = [];

        for (; idx < length; idx++) {
            value = list[idx];
            if (typeof value === "string") {
                valueString = value;
            } else {
                valueString = "" + WEEK_DAYS[value.day];

                if (value.offset) {
                    valueString = value.offset + valueString;
                }
            }

            result.push(valueString);
        }
        return result.toString();
    }

    function getMonthLength(date) {
        var month = date.getMonth();

        if (month === 1) {
            if (new Date(date.getFullYear(), 1, 29).getMonth() === 1) {
                return 29;
            }
            return 28;
        }
        return MONTHS[month];
    }

    function leapYear(year) {
        year = year.getFullYear();
        return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
    }

    function numberSortPredicate(a, b) {
        return a - b;
    }

    function parseExceptions(exceptions, zone) {
        var idx = 0, length, date,
            dates = [];

        if (exceptions) {
            exceptions = exceptions.split(";");
            length = exceptions.length;

            for (; idx < length; idx++) {
                date = parseUTCDate(exceptions[idx], zone);

                if (date) {
                    dates.push(date);
                }
            }
        }

        return dates;
    }

    function isException(exceptions, date, zone) {
        var dates = $.isArray(exceptions) ? exceptions : parseExceptions(exceptions, zone),
            dateTime = date.getTime() - date.getMilliseconds(),
            idx = 0, length = dates.length;

        for (; idx < length; idx++) {
            if (dates[idx].getTime() === dateTime) {
                return true;
            }
        }

        return false;
    }

    function toExceptionString(dates, zone) {
        var idx = 0;
        var length;
        var date;
        var result = [].concat(dates);

        for (length = result.length; idx < length; idx++) {
            date = result[idx];
            date = kendo.timezone.convert(date, zone || date.getTimezoneOffset(), "Etc/UTC");
            result[idx] = kendo.toString(date, RECURRENCE_DATE_FORMAT);
        }

        return result.join(";") + ";";
    }

    function startPeriodByFreq(start, rule) {
        var date = new Date(start);

        switch (rule.freq) {
            case "yearly":
                date.setFullYear(date.getFullYear(), 0, 1);
                break;

            case "monthly":
                date.setFullYear(date.getFullYear(), date.getMonth(), 1);
                break;

            case "weekly":
                setDayOfWeek(date, rule.weekStart, -1);
                break;

            default:
                break;
        }

        if (rule.hours) {
            date.setHours(0);
        }

        if (rule.minutes) {
            date.setMinutes(0);
        }

        if (rule.seconds) {
            date.setSeconds(0);
        }

        return date;
    }

    function endPeriodByFreq(start, rule) {
        var date = new Date(start);

        switch (rule.freq) {
            case "yearly":
                date.setFullYear(date.getFullYear(), 11, 31);
                break;

            case "monthly":
                date.setFullYear(date.getFullYear(), date.getMonth() + 1, 0);
                break;

            case "weekly":
                setDayOfWeek(date, rule.weekStart, -1);
                date.setDate(date.getDate() + 6);
                break;

            default:
                break;
        }

        if (rule.hours) {
            date.setHours(23);
        }

        if (rule.minutes) {
            date.setMinutes(59);
        }

        if (rule.seconds) {
            date.setSeconds(59);
        }

        return date;
    }

    function normalizeEventsByPosition(events, start, rule) {
        var periodEvents = events.slice(rule._startIdx);
        var periodEventsLength = periodEvents.length;
        var positions = rule.positions;
        var list = [];
        var position;
        var event;

        for (var idx = 0, length = positions.length; idx < length; idx++) {
            position = positions[idx];

            if (position < 0) {
                position = periodEventsLength + position;
            } else {
                position -= 1; //convert to zero based index
            }

            event = periodEvents[position];

            if (event && event.start >= start) {
                list.push(event);
            }
        }

        events = events.slice(0, rule._startIdx).concat(list);

        rule._startIdx = events.length;

        return events;
    }

    function expand(event, start, end, zone) {
        var rule = parseRule(event.recurrenceRule, zone),
            startTime, endTime, endDate,
            hours, minutes, seconds,
            durationMS, startPeriod, inPeriod,
            ruleStart, ruleEnd,
            useEventStart, freqName,
            exceptionDates,
            eventStartTime,
            eventStartMS,
            eventStart,
            count, freq,
            positions,
            current,
            events = [];

        if (!rule) {
            return [event];
        }

        positions = rule.positions;
        current = positions ? 0 : 1;

        ruleStart = rule.start;
        ruleEnd = rule.end;

        if (ruleStart || ruleEnd) {
            event = event.clone({
                start: ruleStart ? new Date(ruleStart.value[0]) : undefined,
                end: ruleEnd ? new Date(ruleEnd.value[0]) : undefined
            });
        }

        eventStart = event.start;
        eventStartMS = eventStart.getTime();
        eventStartTime = getMilliseconds(eventStart);

        exceptionDates = parseExceptions(event.recurrenceException, zone);

        if (!exceptionDates[0] && rule.exdates) {
            exceptionDates = rule.exdates.value;
            event.set("recurrenceException", toExceptionString(exceptionDates, zone));
        }

        startPeriod = start = new Date(start);
        end = new Date(end);

        freqName = rule.freq;
        freq = frequencies[freqName];
        count = rule.count;

        if (rule.until && rule.until < end) {
            end = new Date(rule.until);
        }

        useEventStart = freqName === "yearly" || freqName === "monthly" || freqName === "weekly";

        if (start < eventStartMS || count || rule.interval > 1 || useEventStart) {
            start = new Date(eventStartMS);
        } else {
            hours = start.getHours();
            minutes = start.getMinutes();
            seconds = start.getSeconds();

            if (!rule.hours) {
                hours = eventStart.getHours();
            }

            if (!rule.minutes) {
                minutes = eventStart.getMinutes();
            }

            if (!rule.seconds) {
                seconds = eventStart.getSeconds();
            }

            start.setHours(hours, minutes, seconds, eventStart.getMilliseconds());
        }

        rule._startPeriod = new Date(start);

        if (positions) {
            start = startPeriodByFreq(start, rule);
            end = endPeriodByFreq(end, rule);

            var diff = getMilliseconds(end) - getMilliseconds(start);
            if (diff < 0) {
                hours = start.getHours();
                end.setHours(hours, start.getMinutes(), start.getSeconds(), start.getMilliseconds());
                kendoDate.adjustDST(end, hours);
            }

            rule._startPeriod = new Date(start);
            rule._endPeriod = endPeriodByFreq(start, rule);
            rule._startIdx = 0;
        }

        durationMS = event.duration();
        rule._startTime = startTime = kendoDate.toInvariantTime(start);

        if (freq.setup) {
            freq.setup(rule, eventStart, start);
        }

        freq.limit(start, end, rule);

        while (start <= end) {
            endDate = new Date(start);
            setTime(endDate, durationMS);

            inPeriod = start >= startPeriod || endDate > startPeriod;

            if (inPeriod && !isException(exceptionDates, start, zone) || positions) {
                startTime = kendoDate.toUtcTime(kendoDate.getDate(start)) + getMilliseconds(rule._startTime);
                endTime = startTime + durationMS;

                if (eventStartMS !== start.getTime() || eventStartTime !== getMilliseconds(rule._startTime)) {
                    events.push(event.toOccurrence({
                        start: new Date(start),
                        end: endDate,
                        startTime: startTime,
                        endTime: endTime
                    }));
                } else {
                    event.startTime = startTime;
                    event.endTime = endTime;
                    events.push(event);
                }
            }

            if (positions) {
                freq.next(start, rule);
                freq.limit(start, end, rule);

                if (start > rule._endPeriod) {
                    events = normalizeEventsByPosition(events, eventStart, rule);

                    rule._endPeriod = endPeriodByFreq(start, rule);

                    current = events.length;
                }

                if (count && count === current) {
                    break;
                }

            } else {
                if (count && count === current) {
                    break;
                }

                current++;
                freq.next(start, rule);
                freq.limit(start, end, rule);
            }
        }

        return events;
    }

    function parseUTCDate(value, zone) {
        value = kendo.parseDate(value, DATE_FORMATS); //Parse UTC to local time

        if (value && zone) {
            value = timezone.convert(value, value.getTimezoneOffset(), zone);
        }

        return value;
    }

    function parseDateRule(dateRule, zone) {
        var pairs = dateRule.split(";");
        var pair;
        var property;
        var value;
        var tzid;
        var valueIdx, valueLength;

        for (var idx = 0, length = pairs.length; idx < length; idx++) {
            pair = pairs[idx].split(":");
            property = pair[0];
            value = pair[1];

            if (property.indexOf("TZID") !== -1) {
                tzid = property.substring(property.indexOf("TZID")).split("=")[1];
            }

            if (value) {
                value = value.split(",");

                for (valueIdx = 0, valueLength = value.length; valueIdx < valueLength; valueIdx++) {
                    value[valueIdx] = parseUTCDate(value[valueIdx], tzid || zone);
                }
            }
        }

        if (value) {
            return {
                value: value,
                tzid: tzid
            };
        }
    }

    function parseRule(recur, zone) {
        var instance = {};
        var splits, value;
        var idx = 0, length;
        var ruleValue = false;
        var rule, part, parts;
        var property, weekStart, weekDays;
        var predicate = function (a, b) {
            var day1 = a.day,
                day2 = b.day;

            if (day1 < weekStart) {
                day1 += 7;
            }

            if (day2 < weekStart) {
                day2 += 7;
            }

            return day1 - day2;
        };

        if (!recur) {
            return null;
        }

        parts = recur.split("\n");

        if (!parts[1] && (recur.indexOf("DTSTART") !== -1 || recur.indexOf("DTEND") !== -1 || recur.indexOf("EXDATE") !== -1)) {
            parts = recur.split(" ");
        }

        for (idx = 0, length = parts.length; idx < length; idx++) {
            part = parts[idx];

            if (part.indexOf("DTSTART") !== -1) {
                instance.start = parseDateRule(part, zone);
            } else if (part.indexOf("DTEND") !== -1) {
                instance.end = parseDateRule(part, zone);
            } else if (part.indexOf("EXDATE") !== -1) {
                instance.exdates = parseDateRule(part, zone);
            } else if (part.indexOf("RRULE") !== -1) {
                rule = part.substring(6);
            } else { //improve this. support new row at the end of rule
                rule = part;
            }
        }

        rule = rule.split(";");

        for (idx = 0, length = rule.length; idx < length; idx++) {
            property = rule[idx];
            splits = property.split("=");
            value = $.trim(splits[1]).split(",");

            switch ($.trim(splits[0]).toUpperCase()) {
                case "FREQ":
                    instance.freq = value[0].toLowerCase();
                    break;
                case "UNTIL":
                    instance.until = parseUTCDate(value[0], zone);
                    break;
                case "COUNT":
                    instance.count = parseInt(value[0], 10);
                    break;
                case "INTERVAL":
                    instance.interval = parseInt(value[0], 10);
                    break;
                case "BYSECOND":
                    instance.seconds = parseArray(value, { start: 0, end: 60 });
                    ruleValue = true;
                    break;
                case "BYMINUTE":
                    instance.minutes = parseArray(value, { start: 0, end: 59 });
                    ruleValue = true;
                    break;
                case "BYHOUR":
                    instance.hours = parseArray(value, { start: 0, end: 23 });
                    ruleValue = true;
                    break;
                case "BYMONTHDAY":
                    instance.monthDays = parseArray(value, { start: -31, end: 31 });
                    ruleValue = true;
                    break;
                case "BYYEARDAY":
                    instance.yearDays = parseArray(value, { start: -366, end: 366 });
                    ruleValue = true;
                    break;
                case "BYMONTH":
                    instance.months = parseArray(value, { start: 1, end: 12 });
                    ruleValue = true;
                    break;
                case "BYDAY":
                    instance.weekDays = weekDays = parseWeekDayList(value);
                    ruleValue = true;
                    break;
                case "BYWEEKNO":
                    instance.weeks = parseArray(value, { start: -53, end: 53 });
                    ruleValue = true;
                    break;
                case "BYSETPOS":
                    instance.positions = parseArray(value, { start: -366, end: 366 });
                    break;
                case "WKST":
                    instance.weekStart = weekStart = WEEK_DAYS_IDX[value[0]];
                    break;
            }
        }

        if (instance.freq === undefined || (instance.count !== undefined && instance.until)) {
            return null;
        }

        if (!instance.interval) {
            instance.interval = 1;
        }

        if (weekStart === undefined) {
            instance.weekStart = weekStart = kendo.culture().calendar.firstDay;
        }

        if (weekDays) {
            instance.weekDays = weekDays.sort(predicate);
        }

        if (instance.positions && !ruleValue) {
            instance.positions = null;
        }

        instance._hasRuleValue = ruleValue;

        return instance;
    }

    function serializeDateRule(dateRule, zone) {
        var value = dateRule.value;
        var tzid = dateRule.tzid || "";
        var length = value.length;
        var idx = 0;
        var val;

        for (; idx < length; idx++) {
            val = value[idx];
            val = timezone.convert(val, tzid || zone || val.getTimezoneOffset(), "Etc/UTC");
            value[idx] = kendo.toString(val, "yyyyMMddTHHmmssZ");
        }

        if (tzid) {
            tzid = ";TZID=" + tzid;
        }

        return tzid + ":" + value.join(",") + " ";
    }

    function serialize(rule, zone) {
        var weekStart = rule.weekStart;
        var ruleString = "FREQ=" + rule.freq.toUpperCase();
        var exdates = rule.exdates || "";
        var start = rule.start || "";
        var end = rule.end || "";
        var until = rule.until;

        if (rule.interval > 1) {
            ruleString += ";INTERVAL=" + rule.interval;
        }

        if (rule.count) {
            ruleString += ";COUNT=" + rule.count;
        }

        if (until) {
            until = timezone.convert(until, zone || until.getTimezoneOffset(), "Etc/UTC");
            ruleString += ";UNTIL=" + kendo.toString(until, "yyyyMMddTHHmmssZ");
        }

        if (rule.months) {
            ruleString += ";BYMONTH=" + rule.months;
        }

        if (rule.weeks) {
            ruleString += ";BYWEEKNO=" + rule.weeks;
        }

        if (rule.yearDays) {
            ruleString += ";BYYEARDAY=" + rule.yearDays;
        }

        if (rule.monthDays) {
            ruleString += ";BYMONTHDAY=" + rule.monthDays;
        }

        if (rule.weekDays) {
            ruleString += ";BYDAY=" + serializeWeekDayList(rule.weekDays);
        }

        if (rule.hours) {
            ruleString += ";BYHOUR=" + rule.hours;
        }

        if (rule.minutes) {
            ruleString += ";BYMINUTE=" + rule.minutes;
        }

        if (rule.seconds) {
            ruleString += ";BYSECOND=" + rule.seconds;
        }

        if (rule.positions) {
            ruleString += ";BYSETPOS=" + rule.positions;
        }

        if (weekStart !== undefined) {
            ruleString += ";WKST=" + WEEK_DAYS[weekStart];
        }

        if (start) {
            start = "DTSTART" + serializeDateRule(start, zone);
        }

        if (end) {
            end = "DTEND" + serializeDateRule(end, zone);
        }

        if (exdates) {
            exdates = "EXDATE" + serializeDateRule(exdates, zone);
        }

        if (start || end || exdates) {
            ruleString = start + end + exdates + "RRULE:" + ruleString;
        }

        return ruleString;
    }

    kendo.recurrence = {
        rule: {
            parse: parseRule,
            serialize: serialize
        },
        expand: expand,
        dayInYear: dayInYear,
        weekInYear: weekInYear,
        weekInMonth: weekInMonth,
        numberOfWeeks: numberOfWeeks,
        isException: isException,
        toExceptionString: toExceptionString
    };

    var weekDayCheckBoxes = function (firstDay) {
        var shortNames = kendo.culture().calendar.days.namesShort,
            length = shortNames.length,
            result = "",
            idx = 0,
            values = [];

        for (; idx < length; idx++) {
            values.push(idx);
        }

        shortNames = shortNames.slice(firstDay).concat(shortNames.slice(0, firstDay));
        values = values.slice(firstDay).concat(values.slice(0, firstDay));

        for (idx = 0; idx < length; idx++) {
            result += '<label class="k-check"><input class="k-recur-weekday-checkbox" type="checkbox" value="' + values[idx] + '" /> ' + shortNames[idx] + "</label>";
        }

        return result;
    };

    var RECURRENCE_VIEW_TEMPLATE = kendo.template(
       '# if (frequency !== "never") { #' +
           '<div class="k-edit-label"><label>#:messages.repeatEvery#</label></div>' +
           '<div class="k-edit-field"><input class="k-recur-interval"/>#:messages.interval#</div>' +
       '# } #' +
       '# if (frequency === "weekly") { #' +
           '<div class="k-edit-label"><label>#:messages.repeatOn#</label></div>' +
           '<div class="k-edit-field">#=weekDayCheckBoxes(firstWeekDay)#</div>' +
       '# } else if (frequency === "monthly") { #' +
           '<div class="k-edit-label"><label>#:messages.repeatOn#</label></div>' +
           '<div class="k-edit-field">' +
               '<ul class="k-reset">' +
                   '<li>' +
                       '<label><input class="k-recur-month-radio" type="radio" name="month" value="monthday" />#:messages.day#</label>' +
                       '<input class="k-recur-monthday" />' +
                   '</li>' +
                   '<li>' +
                        '<input class="k-recur-month-radio" type="radio" name="month" value="weekday" />' +
                        '<input class="k-recur-weekday-offset" /><input class="k-recur-weekday" />' +
                   '</li>' +
               '</ul>' +
           '</div>' +
       '# } else if (frequency === "yearly") { #' +
           '<div class="k-edit-label"><label>#:messages.repeatOn#</label></div>' +
           '<div class="k-edit-field">' +
               '<ul class="k-reset">' +
                   '<li>' +
                       '<input class="k-recur-year-radio" type="radio" name="year" value="monthday" />' +
                       '<input class="k-recur-month" /><input class="k-recur-monthday" />' +
                   '</li>' +
                   '<li>' +
                       '<input class="k-recur-year-radio" type="radio" name="year" value="weekday" />' +
                       '<input class="k-recur-weekday-offset" /><input class="k-recur-weekday" />#:messages.of#<input class="k-recur-month" />' +
                   '</li>' +
               '</ul>' +
           '</div>' +
       '# } #' +
       '# if (frequency !== "never") { #' +
           '<div class="k-edit-label"><label>#:end.label#</label></div>' +
           '<div class="k-edit-field">' +
               '<ul class="k-reset">' +
                   '<li>' +
                       '<label><input class="k-recur-end-never" type="radio" name="end" value="never" />#:end.never#</label>' +
                   '</li>' +
                   '<li>' +
                       '<label><input class="k-recur-end-count" type="radio" name="end" value="count" />#:end.after#</label>' +
                       '<input class="k-recur-count" />#:end.occurrence#' +
                   '</li>' +
                   '<li>' +
                       '<label><input class="k-recur-end-until" type="radio" name="end" value="until" />#:end.on#</label>' +
                       '<input class="k-recur-until" />' +
                   '</li>' +
               '</ul>' +
           '</div>' +
       '# } #'
    );

    var DAY_RULE = [
        { day: 0, offset: 0 },
        { day: 1, offset: 0 },
        { day: 2, offset: 0 },
        { day: 3, offset: 0 },
        { day: 4, offset: 0 },
        { day: 5, offset: 0 },
        { day: 6, offset: 0 }
    ];

    var WEEKDAY_RULE = [
        { day: 1, offset: 0 },
        { day: 2, offset: 0 },
        { day: 3, offset: 0 },
        { day: 4, offset: 0 },
        { day: 5, offset: 0 }
    ];

    var WEEKEND_RULE = [
        { day: 0, offset: 0 },
        { day: 6, offset: 0 }
    ];

    var BaseRecurrenceEditor = Widget.extend({
        init: function (element, options) {
            var start;
            var that = this;
            var frequencies = options && options.frequencies;

            Widget.fn.init.call(that, element, options);

            that.wrapper = that.element;

            options = that.options;
            options.start = start = options.start || kendoDate.today();

            if (frequencies) {
                options.frequencies = frequencies;
            }

            if (typeof start === "string") {
                options.start = kendo.parseDate(start, "yyyyMMddTHHmmss");
            }

            if (options.firstWeekDay === null) {
                options.firstWeekDay = kendo.culture().calendar.firstDay;
            }

            that._namespace = "." + options.name;
        },

        options: {
            value: "",
            start: "",
            timezone: "",
            spinners: true,
            firstWeekDay: null,
            frequencies: [
                "never",
                "daily",
                "weekly",
                "monthly",
                "yearly"
            ],
            mobile: false,
            messages: {
                frequencies: {
                    never: "Never",
                    hourly: "Hourly",
                    daily: "Daily",
                    weekly: "Weekly",
                    monthly: "Monthly",
                    yearly: "Yearly"
                },
                hourly: {
                    repeatEvery: "Repeat every: ",
                    interval: " hour(s)"
                },
                daily: {
                    repeatEvery: "Repeat every: ",
                    interval: " day(s)"
                },
                weekly: {
                    interval: " week(s)",
                    repeatEvery: "Repeat every: ",
                    repeatOn: "Repeat on: "
                },
                monthly: {
                    repeatEvery: "Repeat every: ",
                    repeatOn: "Repeat on: ",
                    interval: " month(s)",
                    day: "Day "
                },
                yearly: {
                    repeatEvery: "Repeat every: ",
                    repeatOn: "Repeat on: ",
                    interval: " year(s)",
                    of: " of "
                },
                end: {
                    label: "End:",
                    mobileLabel: "Ends",
                    never: "Never",
                    after: "After ",
                    occurrence: " occurrence(s)",
                    on: "On "
                },
                offsetPositions: {
                    first: "first",
                    second: "second",
                    third: "third",
                    fourth: "fourth",
                    last: "last"
                },
                weekdays: {
                    day: "day",
                    weekday: "weekday",
                    weekend: "weekend day"
                }
            }
        },

        events: ["change"],

        _initInterval: function () {
            var that = this;
            var rule = that._value;

            that._container
                .find(".k-recur-interval")
                .kendoNumericTextBox({
                    spinners: that.options.spinners,
                    value: rule.interval || 1,
                    decimals: 0,
                    format: "#",
                    min: 1,
                    change: function () {
                        rule.interval = this.value();
                        that._trigger();
                    }
                });
        },

        _weekDayRule: function (clear) {
            var that = this;
            var weekday = (that._weekDay.element || that._weekDay).val();
            var offset = Number((that._weekDayOffset.element || that._weekDayOffset).val());
            var weekDays = null;
            var positions = null;

            if (!clear) {
                if (weekday === "day") {
                    weekDays = DAY_RULE;
                    positions = offset;
                } else if (weekday === "weekday") {
                    weekDays = WEEKDAY_RULE;
                    positions = offset;
                } else if (weekday === "weekend") {
                    weekDays = WEEKEND_RULE;
                    positions = offset;
                } else {
                    weekDays = [{
                        offset: offset,
                        day: Number(weekday)
                    }];
                }
            }

            that._value.weekDays = weekDays;
            that._value.positions = positions;
        },

        _weekDayView: function () {
            var that = this;
            var weekDays = that._value.weekDays;
            var positions = that._value.positions;
            var weekDayOffsetWidget = that._weekDayOffset;
            var weekDayOffset;
            var weekDayValue;
            var length;
            var method;

            if (weekDays) {
                length = weekDays.length;

                if (positions) {
                    if (length === 7) {
                        weekDayValue = "day";
                        weekDayOffset = positions;
                    } else if (length === 5) {
                        weekDayValue = "weekday";
                        weekDayOffset = positions;
                    } else if (length === 2) {
                        weekDayValue = "weekend";
                        weekDayOffset = positions;
                    }
                }

                if (!weekDayValue) {
                    weekDays = weekDays[0];
                    weekDayValue = weekDays.day;
                    weekDayOffset = weekDays.offset || "";
                }

                method = weekDayOffsetWidget.value ? "value" : "val";

                weekDayOffsetWidget[method](weekDayOffset);
                that._weekDay[method](weekDayValue);
            }
        },

        _initWeekDay: function () {
            var that = this, data;

            var weekdayMessage = that.options.messages.weekdays;
            var offsetMessage = that.options.messages.offsetPositions;

            var weekDayInput = that._container.find(".k-recur-weekday");

            var change = function () {
                that._weekDayRule();
                that._trigger();
            };

            if (weekDayInput[0]) {
                that._weekDayOffset = new DropDownList(that._container.find(".k-recur-weekday-offset"), {
                    change: change,
                    dataTextField: "text",
                    dataValueField: "value",
                    dataSource: [
                        { text: offsetMessage.first, value: "1" },
                        { text: offsetMessage.second, value: "2" },
                        { text: offsetMessage.third, value: "3" },
                        { text: offsetMessage.fourth, value: "4" },
                        { text: offsetMessage.last, value: "-1" }
                    ]
                });

                data = [
                    { text: weekdayMessage.day, value: "day" },
                    { text: weekdayMessage.weekday, value: "weekday" },
                    { text: weekdayMessage.weekend, value: "weekend" }
                ];

                that._weekDay = new DropDownList(weekDayInput, {
                    value: that.options.start.getDay(),
                    change: change,
                    dataTextField: "text",
                    dataValueField: "value",
                    dataSource: data.concat($.map(kendo.culture().calendar.days.names, function (dayName, idx) {
                        return {
                            text: dayName,
                            value: idx
                        };
                    }))
                });

                that._weekDayView();
            }
        },

        _initWeekDays: function () {
            var that = this;
            var rule = that._value;
            var weekDays = that._container.find(".k-recur-weekday-checkbox");

            if (weekDays[0]) {
                weekDays.on(CLICK + that._namespace, function () {
                    rule.weekDays = $.map(weekDays.filter(":checked"), function (checkbox) {
                        return {
                            day: Number(checkbox.value),
                            offset: 0
                        };
                    });

                    if (!that.options.mobile) {
                        that._trigger();
                    }
                });

                if (rule.weekDays) {
                    var idx, weekDay;
                    var i = 0, l = weekDays.length;
                    var length = rule.weekDays.length;

                    for (; i < l; i++) {
                        weekDay = weekDays[i];
                        for (idx = 0; idx < length; idx++) {
                            if (weekDay.value == rule.weekDays[idx].day) {
                                weekDay.checked = true;
                            }
                        }
                    }
                }
            }
        },

        _initMonthDay: function () {
            var that = this;
            var rule = that._value;
            var monthDayInput = that._container.find(".k-recur-monthday");

            if (monthDayInput[0]) {
                that._monthDay = new kendo.ui.NumericTextBox(monthDayInput, {
                    spinners: that.options.spinners,
                    min: 1,
                    max: 31,
                    decimals: 0,
                    format: "#",
                    value: rule.monthDays ? rule.monthDays[0] : that.options.start.getDate(),
                    change: function () {
                        var value = this.value();

                        rule.monthDays = value ? [value] : value;
                        that._trigger();
                    }
                });
            }
        },

        _initCount: function () {
            var that = this,
                input = that._container.find(".k-recur-count"),
                rule = that._value;

            that._count = input.kendoNumericTextBox({
                spinners: that.options.spinners,
                value: rule.count || 1,
                decimals: 0,
                format: "#",
                min: 1,
                change: function () {
                    rule.count = this.value();
                    that._trigger();
                }
            }).data("kendoNumericTextBox");
        },

        _initUntil: function () {
            var that = this,
                input = that._container.find(".k-recur-until"),
                start = that.options.start,
                rule = that._value,
                until = rule.until;

            that._until = input.kendoDatePicker({
                min: until && until < start ? until : start,
                value: until || start,
                change: function () {
                    rule.until = this.value();
                    that._trigger();
                }
            }).data("kendoDatePicker");
        },

        _trigger: function () {
            if (!this.options.mobile) {
                this.trigger("change");
            }
        }
    });

    var RecurrenceEditor = BaseRecurrenceEditor.extend({
        init: function (element, options) {
            var that = this;

            BaseRecurrenceEditor.fn.init.call(that, element, options);

            that._initFrequency();

            that._initContainer();

            that.value(that.options.value);
        },

        options: {
            name: "RecurrenceEditor"
        },

        events: ["change"],

        destroy: function () {
            var that = this;

            that._frequency.destroy();
            that._container.find("input[type=radio],input[type=checkbox]").off(CLICK + that._namespace);

            kendo.destroy(that._container);

            BaseRecurrenceEditor.fn.destroy.call(that);
        },

        value: function (value) {
            var that = this,
                timezone = that.options.timezone;

            if (value === undefined) {
                if (!that._value.freq) {
                    return "";
                }

                return serialize(that._value, timezone);
            }

            that._value = parseRule(value, timezone) || {};

            that._frequency.value(that._value.freq || "");
            that._initView(that._frequency.value());
        },

        _initContainer: function () {
            var element = this.element,
                container = $('<div class="k-recur-view" />'),
                editContainer = element.parent(".k-edit-field");

            if (editContainer[0]) {
                container.insertAfter(editContainer);
            } else {
                element.append(container);
            }

            this._container = container;
        },

        _initFrequency: function () {
            var that = this,
                options = that.options,
                frequencies = options.frequencies,
                messages = options.messages.frequencies,
                ddl = $('<input />'),
                frequency;

            frequencies = $.map(frequencies, function (frequency) {
                return {
                    text: messages[frequency],
                    value: frequency
                };
            });

            frequency = frequencies[0];
            if (frequency && frequency.value === "never") {
                frequency.value = "";
            }

            that.element.append(ddl);
            that._frequency = new DropDownList(ddl, {
                dataTextField: "text",
                dataValueField: "value",
                dataSource: frequencies,
                change: function () {
                    that._value = {};
                    that._initView(that._frequency.value());
                    that.trigger("change");
                }
            });
        },

        _initView: function (frequency) {
            var that = this;
            var rule = that._value;
            var options = that.options;

            var data = {
                frequency: frequency || "never",
                weekDayCheckBoxes: weekDayCheckBoxes,
                firstWeekDay: options.firstWeekDay,
                messages: options.messages[frequency],
                end: options.messages.end
            };

            kendo.destroy(that._container);
            that._container.html(RECURRENCE_VIEW_TEMPLATE(data));

            if (!frequency) {
                that._value = {};
                return;
            }

            rule.freq = frequency;

            if (frequency === "weekly" && !rule.weekDays) {
                rule.weekDays = [{
                    day: options.start.getDay(),
                    offset: 0
                }];
            }

            that._initInterval();
            that._initWeekDays();
            that._initMonthDay();
            that._initWeekDay();
            that._initMonth();
            that._initCount();
            that._initUntil();

            that._period();
            that._end();
        },

        _initMonth: function () {
            var that = this;
            var rule = that._value;
            var month = rule.months || [that.options.start.getMonth() + 1];
            var monthInputs = that._container.find(".k-recur-month");
            var options;

            if (monthInputs[0]) {
                options = {
                    change: function () {
                        rule.months = [Number(this.value())];
                        that.trigger("change");
                    },
                    dataTextField: "text",
                    dataValueField: "value",
                    dataSource: $.map(kendo.culture().calendar.months.names, function (monthName, idx) {
                        return {
                            text: monthName,
                            value: idx + 1
                        };
                    })
                };

                that._month1 = new DropDownList(monthInputs[0], options);
                that._month2 = new DropDownList(monthInputs[1], options);

                if (month) {
                    month = month[0];
                    that._month1.value(month);
                    that._month2.value(month);
                }
            }

        },

        _end: function () {
            var that = this;
            var rule = that._value;
            var container = that._container;
            var namespace = that._namespace;
            var click = function (e) {
                that._toggleEnd(e.currentTarget.value);
                that.trigger("change");
            };
            var endRule;

            that._buttonNever = container.find(".k-recur-end-never").on(CLICK + namespace, click);
            that._buttonCount = container.find(".k-recur-end-count").on(CLICK + namespace, click);
            that._buttonUntil = container.find(".k-recur-end-until").on(CLICK + namespace, click);

            if (rule.count) {
                endRule = "count";
            } else if (rule.until) {
                endRule = "until";
            }

            that._toggleEnd(endRule);
        },

        _period: function () {
            var that = this;
            var rule = that._value;
            var monthly = rule.freq === "monthly";

            var toggleRule = monthly ? that._toggleMonthDay : that._toggleYear;

            var selector = ".k-recur-" + (monthly ? "month" : "year") + "-radio";
            var radioButtons = that._container.find(selector);

            if (!monthly && rule.freq !== "yearly") {
                return;
            }

            radioButtons.on(CLICK + that._namespace, function (e) {
                toggleRule.call(that, e.currentTarget.value);
                that.trigger("change");
            });

            that._buttonMonthDay = radioButtons.eq(0);
            that._buttonWeekDay = radioButtons.eq(1);

            toggleRule.call(that, rule.weekDays ? "weekday" : "monthday");
        },

        _toggleEnd: function (endRule) {
            var that = this;
            var count, until;
            var enableCount, enableUntil;

            if (endRule === "count") {
                that._buttonCount.prop("checked", true);

                enableCount = true;
                enableUntil = false;

                count = that._count.value();
                until = null;
            } else if (endRule === "until") {
                that._buttonUntil.prop("checked", true);

                enableCount = false;
                enableUntil = true;

                count = null;
                until = that._until.value();
            } else {
                that._buttonNever.prop("checked", true);

                enableCount = enableUntil = false;
                count = until = null;
            }

            that._count.enable(enableCount);
            that._until.enable(enableUntil);

            that._value.count = count;
            that._value.until = until;
        },

        _toggleMonthDay: function (monthRule) {
            var that = this;
            var enableMonthDay = false;
            var enableWeekDay = true;
            var clear = false;
            var monthDays;

            if (monthRule === "monthday") {
                that._buttonMonthDay.prop("checked", true);

                monthDays = [that._monthDay.value()];

                enableMonthDay = true;
                enableWeekDay = false;
                clear = true;
            } else {
                that._buttonWeekDay.prop("checked", true);
                monthDays = null;
            }

            that._weekDay.enable(enableWeekDay);
            that._weekDayOffset.enable(enableWeekDay);
            that._monthDay.enable(enableMonthDay);

            that._value.monthDays = monthDays;

            that._weekDayRule(clear);
        },

        _toggleYear: function (yearRule) {
            var that = this;
            var enableMonth1 = false;
            var enableMonth2 = true;
            var month;

            if (yearRule === "monthday") {
                enableMonth1 = true;
                enableMonth2 = false;

                month = that._month1.value();
            } else {
                month = that._month2.value();
            }

            that._month1.enable(enableMonth1);
            that._month2.enable(enableMonth2);

            that._value.months = [month];
            that._toggleMonthDay(yearRule);
        }
    });

    ui.plugin(RecurrenceEditor);


    var RECURRENCE_HEADER_TEMPLATE = kendo.template('<div class="k-edit-label"><label>#:headerTitle#</label></div>' +
      '<div class="k-edit-field k-recur-pattern k-scheduler-toolbar"></div>' +
      '<div class="k-recur-view"></div>'
    );

    var RECURRENCE_REPEAT_PATTERN_TEMPLATE = kendo.template(
       '# if (frequency !== "never") { #' +
           '<div class="k-edit-label"><label>#:messages.repeatEvery#</label></div>' +
           '<div class="k-edit-field"><input class="k-recur-interval" pattern="\\\\d*"/>#:messages.interval#</div>' +
       '# } #' +
       '# if (frequency === "weekly") { #' +
           '<div class="k-edit-label"><label>#:messages.repeatOn#</label></div>' +
           '<div class="k-edit-field">#=weekDayCheckBoxes(firstWeekDay)#</div>' +
       '# } else if (frequency === "monthly") { #' +
           '<div class="k-edit-label"><label>#:messages.repeatBy#</label></div>' +
           '<div class="k-edit-field k-scheduler-toolbar k-repeat-rule"></div>' +
           '<div class="k-monthday-view" style="display:none">' +
               '<div class="k-edit-label"><label>#:messages.day#</label></div>' +
               '<div class="k-edit-field"><input class="k-recur-monthday" pattern="\\\\d*"/></div>' +
           '</div>' +
           '<div class="k-weekday-view" style="display:none">' +
               '<div class="k-edit-label"><label>#:messages.every#</label></div>' +
               '<div class="k-edit-field"><select class="k-recur-weekday-offset"></select></div>' +
               '<div class="k-edit-label"><label>#:messages.day#</label></div>' +
               '<div class="k-edit-field"><select class="k-recur-weekday"></select></div>' +
           '</div>' +
       '# } else if (frequency === "yearly") { #' +
           '<div class="k-edit-label"><label>#:messages.repeatBy#</label></div>' +
           '<div class="k-edit-field k-scheduler-toolbar k-repeat-rule"></div>' +
           '<div class="k-monthday-view" style="display:none">' +
               '<div class="k-edit-label"><label>#:messages.day#</label></div>' +
               '<div class="k-edit-field"><input class="k-recur-monthday" pattern="\\\\d*"/></div>' +
           '</div>' +
           '<div class="k-weekday-view" style="display:none">' +
               '<div class="k-edit-label"><label>#:messages.every#</label></div>' +
               '<div class="k-edit-field"><select class="k-recur-weekday-offset"></select></div>' +
               '<div class="k-edit-label"><label>#:messages.day#</label></div>' +
               '<div class="k-edit-field"><select class="k-recur-weekday"></select></div>' +
           '</div>' +
           '<div class="k-edit-label"><label>#:messages.month#</label></div>' +
           '<div class="k-edit-field"><select class="k-recur-month"></select></div>' +
       '# } #'
    );

    var RECURRENCE_END_PATTERN_TEMPLATE = kendo.template(
        '# if (endPattern === "count") { #' +
           '<div class="k-edit-label"><label>#:messages.after#</label></div>' +
           '<div class="k-edit-field"><input class="k-recur-count" pattern="\\\\d*" /></div>' +
        '# } else if (endPattern === "until") { #' +
           '<div class="k-edit-label"><label>#:messages.on#</label></div>' +
           '<div class="k-edit-field"><input type="date" class="k-recur-until" /></div>' +
        '# } #'
    );

    var RECURRENCE_GROUP_BUTTON_TEMPLATE = kendo.template(
        '<ul class="k-reset k-header k-scheduler-navigation">' +
            '#for (var i = 0, length = dataSource.length; i < length; i++) {#' +
                '<li class="k-state-default #= value === dataSource[i].value ? \"k-state-selected\" : \"\" #">' +
                    '<a role="button" href="\\#" class="k-link" data-#=ns#value="#=dataSource[i].value#">#:dataSource[i].text#</a>' +
                '</li>' +
            '#}#' +
        '</ul>'
    );

    var MobileRecurrenceEditor = BaseRecurrenceEditor.extend({
        init: function (element, options) {
            var that = this;

            BaseRecurrenceEditor.fn.init.call(that, element, options);

            options = that.options;

            that._optionTemplate = kendo.template('<option value="#:value#">#:text#</option>');

            that.value(options.value);

            that._pane = options.pane;

            that._initRepeatButton();

            that._initRepeatEnd();

            that._defaultValue = that._value;
        },

        options: {
            name: "MobileRecurrenceEditor",
            animations: {
                left: "slide",
                right: "slide:right"
            },
            mobile: true,
            messages: {
                cancel: "Cancel",
                update: "Save",
                endTitle: "Repeat ends",
                repeatTitle: "Repeat pattern",
                headerTitle: "Repeat event",
                end: {
                    patterns: {
                        never: "Never",
                        after: "After...",
                        on: "On..."
                    },
                    never: "Never",
                    after: "End repeat after",
                    on: "End repeat on"
                },
                daily: {
                    interval: ""
                },
                hourly: {
                    interval: ""
                },
                weekly: {
                    interval: ""
                },
                monthly: {
                    interval: "",
                    repeatBy: "Repeat by: ",
                    dayOfMonth: "Day of the month",
                    dayOfWeek: "Day of the week",
                    repeatEvery: "Repeat every",
                    every: "Every",
                    day: "Day "
                },
                yearly: {
                    interval: "",
                    repeatBy: "Repeat by: ",
                    dayOfMonth: "Day of the month",
                    dayOfWeek: "Day of the week",
                    repeatEvery: "Repeat every: ",
                    every: "Every",
                    month: "Month",
                    day: "Day"
                }
            }
        },

        events: ["change"],

        value: function (value) {
            var that = this;
            var timezone = that.options.timezone;

            if (value === undefined) {
                if (!that._value.freq) {
                    return "";
                }

                return serialize(that._value, timezone);
            }

            that._value = parseRule(value, timezone) || {};
        },

        destroy: function () {
            this._destroyView();

            kendo.destroy(this._endFields);

            this._repeatButton.off(CLICK + this._namespace);

            BaseRecurrenceEditor.fn.destroy.call(this);
        },

        _initRepeatButton: function () {
            var that = this;
            var freq = that.options.messages.frequencies[this._value.freq || "never"];

            that._repeatButton = $('<a href="#" class="k-button k-scheduler-recur">' + freq + '</a>')
                                    .on(CLICK + that._namespace, function (e) {
                                        e.preventDefault();
                                        that._createView("repeat");
                                        that._pane.navigate("recurrence", that.options.animations.left);
                                    });

            that.element.append(that._repeatButton);
        },

        _initRepeatEnd: function () {
            var that = this;

            var endLabelField = $('<div class="k-edit-label"><label>' + that.options.messages.end.mobileLabel + '</label></div>').insertAfter(that.element.parent(".k-edit-field"));

            var endEditField = $('<div class="k-edit-field"><a href="#" class="k-button k-scheduler-recur-end"></a></div>')
                .on(CLICK + that._namespace, function (e) {
                    e.preventDefault();

                    if (!that._value.freq) {
                        return;
                    }

                    that._createView("end");
                    that._pane.navigate("recurrence", that.options.animations.left);
                })
                .insertAfter(endLabelField);

            that._endFields = endLabelField.add(endEditField).toggleClass("k-state-disabled", !that._value.freq);
            that._endButton = endEditField.find(".k-scheduler-recur-end").text(that._endText());
        },

        _endText: function () {
            var rule = this._value;
            var messages = this.options.messages.end;

            var text = messages.never;

            if (rule.count) {
                text = kendo.format("{0} {1}", messages.after, rule.count);
            } else if (rule.until) {
                text = kendo.format("{0} {1:d}", messages.on, rule.until);
            }

            return text;
        },

        _initFrequency: function () {
            var that = this;
            var frequencyMessages = that.options.messages.frequencies;

            var html = RECURRENCE_GROUP_BUTTON_TEMPLATE({
                dataSource: $.map(this.options.frequencies, function (frequency) {
                    return {
                        text: frequencyMessages[frequency],
                        value: frequency !== "never" ? frequency : ""
                    };
                }),
                value: that._value.freq || "",
                ns: kendo.ns
            });

            that._view.element
                .find(".k-recur-pattern")
                .append(html)
                .on(CLICK + that._namespace, ".k-scheduler-navigation li", function (e) {
                    var li = $(this);

                    e.preventDefault();

                    li.addClass("k-state-selected")
                      .siblings().removeClass("k-state-selected");

                    that._value = { freq: li.children("a").attr(kendo.attr("value")) };

                    that._initRepeatView();
                });
        },

        _initEndNavigation: function () {
            var that = this;
            var endMessages = that.options.messages.end.patterns;
            var rule = that._value;
            var value = "";

            if (rule.count) {
                value = "count";
            } else if (rule.until) {
                value = "until";
            }

            var html = RECURRENCE_GROUP_BUTTON_TEMPLATE({
                dataSource: [
                    { text: endMessages.never, value: "" },
                    { text: endMessages.after, value: "count" },
                    { text: endMessages.on, value: "until" }
                ],
                value: value,
                ns: kendo.ns
            });

            that._view.element
                .find(".k-recur-pattern")
                .append(html)
                .on(CLICK + that._namespace, ".k-scheduler-navigation li", function (e) {
                    var li = $(this);
                    var count = null;
                    var until = null;

                    e.preventDefault();

                    li.addClass("k-state-selected")
                      .siblings().removeClass("k-state-selected");

                    that._initEndView(li.children("a").attr(kendo.attr("value")));

                    if (that._count) {
                        count = that._count.value();
                        until = null;
                    } else if (that._until) {
                        count = null;
                        until = that._until.val ? kendo.parseDate(that._until.val(), "yyyy-MM-dd") : that._until.value();
                    }

                    rule.count = count;
                    rule.until = until;
                });
        },

        _createView: function (viewType) {
            var that = this;
            var options = that.options;
            var messages = options.messages;
            var headerTitle = messages[viewType === "repeat" ? "repeatTitle" : "endTitle"];

            var html = '<div data-role="view" class="k-popup-edit-form k-scheduler-edit-form k-mobile-list" id="recurrence">' +
                       '<div data-role="header" class="k-header">' +
                           '<a href="#" class="k-button k-scheduler-cancel">' + messages.cancel + '</a>' +
                               messages.headerTitle +
                           '<a href="#" class="k-button k-scheduler-update">' + messages.update + '</a>' +
                       '</div>';

            var returnViewId = that._pane.view().id;

            that._view = that._pane.append(html + RECURRENCE_HEADER_TEMPLATE({ headerTitle: headerTitle }));

            that._view.element.on(CLICK + that._namespace, "a.k-scheduler-cancel, a.k-scheduler-update", function (e) {
                e.preventDefault();
                e.stopPropagation();

                if ($(this).hasClass("k-scheduler-update")) {
                    that.trigger("change");
                    that._defaultValue = $.extend({}, that._value);
                } else {
                    that._value = that._defaultValue;
                }

                var frequency = that._value.freq;

                that._endButton.text(that._endText());
                that._endFields.toggleClass("k-state-disabled", !frequency);
                that._repeatButton.text(messages.frequencies[frequency || "never"]);

                that._pane.one("viewShow", function () {
                    that._destroyView();
                });

                that._pane.navigate(returnViewId, that.options.animations.right);
            });

            that._container = that._view.element.find(".k-recur-view");

            if (viewType === "repeat") {
                that._initFrequency();
                that._initRepeatView();
            } else {
                that._initEndNavigation();
                that._initEndView();
            }
        },

        _destroyView: function () {
            if (this._view) {
                this._view.destroy();
                this._view.element.remove();
            }

            this._view = null;
        },

        _initRepeatView: function () {
            var that = this;
            var frequency = that._value.freq || "never";

            var data = {
                frequency: frequency,
                weekDayCheckBoxes: weekDayCheckBoxes,
                firstWeekDay: that.options.firstWeekDay,
                messages: that.options.messages[frequency]
            };

            var html = RECURRENCE_REPEAT_PATTERN_TEMPLATE(data);
            var container = that._container;
            var rule = that._value;

            kendo.destroy(container);
            container.html(html);

            if (!html) {
                that._value = {};
                return;
            }

            if (frequency === "weekly" && !rule.weekDays) {
                rule.weekDays = [{
                    day: that.options.start.getDay(),
                    offset: 0
                }];
            }

            that._initInterval();
            that._initMonthDay();
            that._initWeekDays();
            that._initWeekDay();
            that._initMonth();

            that._period();
        },

        _initEndView: function (endPattern) {
            var that = this;
            var rule = that._value;

            if (endPattern === undefined) {
                if (rule.count) {
                    endPattern = "count";
                } else if (rule.until) {
                    endPattern = "until";
                }
            }

            var data = {
                endPattern: endPattern,
                messages: that.options.messages.end
            };

            kendo.destroy(that._container);
            that._container.html(RECURRENCE_END_PATTERN_TEMPLATE(data));

            that._initCount();
            that._initUntil();
        },

        _initWeekDay: function () {
            var that = this, data;

            var weekdayMessage = that.options.messages.weekdays;
            var offsetMessage = that.options.messages.offsetPositions;

            var weekDaySelect = that._container.find(".k-recur-weekday");

            var change = function () {
                that._weekDayRule();
                that.trigger("change");
            };

            if (weekDaySelect[0]) {
                that._weekDayOffset = that._container.find(".k-recur-weekday-offset")
                                          .html(that._options([
                                            { text: offsetMessage.first, value: "1" },
                                            { text: offsetMessage.second, value: "2" },
                                            { text: offsetMessage.third, value: "3" },
                                            { text: offsetMessage.fourth, value: "4" },
                                            { text: offsetMessage.last, value: "-1" }
                                          ]))
                                          .change(change);

                data = [
                    { text: weekdayMessage.day, value: "day" },
                    { text: weekdayMessage.weekday, value: "weekday" },
                    { text: weekdayMessage.weekend, value: "weekend" }
                ];

                data = data.concat($.map(kendo.culture().calendar.days.names, function (dayName, idx) {
                    return {
                        text: dayName,
                        value: idx
                    };
                }));

                that._weekDay = weekDaySelect.html(that._options(data))
                                             .change(change)
                                             .val(that.options.start.getDay());

                that._weekDayView();
            }
        },

        _initMonth: function () {
            var that = this;
            var rule = that._value;
            var start = that.options.start;
            var month = rule.months || [start.getMonth() + 1];
            var monthSelect = that._container.find(".k-recur-month");
            var monthNames = kendo.culture().calendar.months.names;

            if (monthSelect[0]) {
                var data = $.map(monthNames, function (monthName, idx) {
                    return {
                        text: monthName,
                        value: idx + 1
                    };
                });

                monthSelect.html(that._options(data))
                           .change(function () {
                               rule.months = [Number(this.value)];
                           });

                that._monthSelect = monthSelect;

                if (month) {
                    monthSelect.val(month[0]);
                }
            }

        },

        _period: function () {
            var that = this;
            var rule = that._value;
            var container = that._container;
            var messages = that.options.messages[rule.freq];
            var repeatRuleGroupButton = container.find(".k-repeat-rule");
            var weekDayView = container.find(".k-weekday-view");
            var monthDayView = container.find(".k-monthday-view");

            if (repeatRuleGroupButton[0]) {
                var currentValue = rule.weekDays ? "weekday" : "monthday";

                var html = RECURRENCE_GROUP_BUTTON_TEMPLATE({
                    value: currentValue,
                    dataSource: [
                        { text: messages.dayOfMonth, value: "monthday" },
                        { text: messages.dayOfWeek, value: "weekday" }
                    ],
                    ns: kendo.ns
                });

                var init = function (val) {
                    var weekDayName = that._weekDay.val();
                    var weekDayOffset = that._weekDayOffset.val();
                    var monthDay = that._monthDay.value();
                    var month = that._monthSelect ? that._monthSelect.val() : null;

                    if (val === "monthday") {
                        rule.weekDays = null;
                        rule.monthDays = monthDay ? [monthDay] : monthDay;
                        rule.months = month ? [Number(month)] : month;

                        weekDayView.hide();
                        monthDayView.show();
                    } else {
                        rule.monthDays = null;
                        rule.months = month ? [Number(month)] : month;

                        rule.weekDays = [{
                            offset: Number(weekDayOffset),
                            day: Number(weekDayName)
                        }];

                        weekDayView.show();
                        monthDayView.hide();
                    }
                };

                repeatRuleGroupButton
                    .append(html)
                    .on(CLICK + that._namespace, ".k-scheduler-navigation li", function (e) {
                        var li = $(this).addClass("k-state-selected");

                        e.preventDefault();

                        li.siblings().removeClass("k-state-selected");

                        var value = li.children("a").attr(kendo.attr("value"));

                        init(value);
                    });

                init(currentValue);
            }
        },

        _initUntil: function () {
            var that = this;
            var input = that._container.find(".k-recur-until");
            var start = that.options.start;
            var rule = that._value;
            var until = rule.until;
            var min = until && until < start ? until : start;

            if (kendo.support.input.date) {
                that._until = input.attr("min", kendo.toString(min, "yyyy-MM-dd"))
                                   .val(kendo.toString(until || start, "yyyy-MM-dd"))
                                   .on("change", function () {
                                       rule.until = kendo.parseDate(this.value, "yyyy-MM-dd");
                                   });
            } else {
                that._until = input.kendoDatePicker({
                    min: min,
                    value: until || start,
                    change: function () {
                        rule.until = this.value();
                    }
                }).data("kendoDatePicker");
            }
        },

        _options: function (data, optionLabel) {
            var idx = 0;
            var html = "";
            var length = data.length;
            var template = this._optionTemplate;

            if (optionLabel) {
                html += template({ value: "", text: optionLabel });
            }

            for (; idx < length; idx++) {
                html += template(data[idx]);
            }

            return html;
        }
    });

    ui.plugin(MobileRecurrenceEditor);

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        setTime = kendo.date.setTime,
        SchedulerView = ui.SchedulerView,
        extend = $.extend,
        proxy = $.proxy,
        getDate = kendo.date.getDate,
        getMilliseconds = kendo.date.getMilliseconds,
        MS_PER_DAY = kendo.date.MS_PER_DAY,
        MS_PER_MINUTE = kendo.date.MS_PER_MINUTE,
        NS = ".kendoTimelineView";

    var EVENT_TEMPLATE = kendo.template('<div>' +
        '<div class="k-event-template k-event-time">#:kendo.format("{0:t} - {1:t}", start, end)#</div>' +
        '<div class="k-event-template">${title}</div></div>'),
        DATA_HEADER_TEMPLATE = kendo.template("<span class='k-link k-nav-day'>#=kendo.toString(date, 'ddd M/dd')#</span>"),
        EVENT_WRAPPER_STRING = '<div role="gridcell" aria-selected="false" ' +
                'data-#=ns#uid="#=uid#"' +
                '#if (resources[0]) { #' +
                    'style="background-color:#=resources[0].color#; border-color: #=resources[0].color#"' +
                    'class="k-event#=inverseColor ? " k-event-inverse" : ""#" ' +
                '#} else {#' +
                    'class="k-event"' +
                '#}#' +
                '>' +
                '<span class="k-event-actions">' +
                    '# if(data.tail) {#' +
                        '<span class="k-icon k-i-arrow-w"></span>' +
                    '#}#' +
                    '# if(data.isException()) {#' +
                        '<span class="k-icon k-i-exception"></span>' +
                    '# } else if(data.isRecurring()) {#' +
                        '<span class="k-icon k-i-refresh"></span>' +
                    '# } #' +
                '</span>' +
                '{0}' +
                '<span class="k-event-actions">' +
                    '#if (showDelete) {#' +
                        '<a href="\\#" class="k-link k-event-delete"><span class="k-icon k-si-close"></span></a>' +
                    '#}#' +
                    '# if(data.head) {#' +
                        '<span class="k-icon k-i-arrow-e"></span>' +
                    '#}#' +
                '</span>' +
                '#if(resizable && !data.tail){#' +
                '<span class="k-resize-handle k-resize-w"></span>' +
                '#}#' +
                '#if(resizable && !data.head){#' +
                '<span class="k-resize-handle k-resize-e"></span>' +
                '#}#' +
                '</div>';

    function toInvariantTime(date) {
        var staticDate = new Date(1980, 1, 1, 0, 0, 0);
        setTime(staticDate, getMilliseconds(date));
        return staticDate;
    }

    function isInDateRange(value, min, max) {
        return value >= min && value <= max;
    }

    function getWorkDays(options) {
        var workDays = [];
        var dayIndex = options.workWeekStart;

        workDays.push(dayIndex);

        while (options.workWeekEnd != dayIndex) {
            if (dayIndex > 6) {
                dayIndex -= 7;
            } else {
                dayIndex++;
            }
            workDays.push(dayIndex);
        }
        return workDays;
    }

    function setColspan(columnLevel) {
        var count = 0;
        if (columnLevel.columns) {
            for (var i = 0; i < columnLevel.columns.length; i++) {
                count += setColspan(columnLevel.columns[i]);
            }
            columnLevel.colspan = count;
            return count;
        } else {
            columnLevel.colspan = 1;
            return 1;
        }
    }

    var TimelineView = SchedulerView.extend({
        init: function (element, options) {
            var that = this;

            SchedulerView.fn.init.call(that, element, options);

            that.title = that.options.title || that.options.name;

            that._workDays = getWorkDays(that.options);

            that._templates();

            that._editable();

            that.calculateDateRange();

            that._groups();

            that._currentTime();
        },
        name: "timeline",

        _currentTimeMarkerUpdater: function () {
            var currentTime = new Date();
            var options = this.options;

            this.datesHeader.find(".k-current-time").remove();

            if (!this._isInDateSlot({ start: currentTime, end: currentTime })) {
                return;
            }

            if (options.currentTimeMarker.useLocalTimezone === false) {
                var timezone = options.dataSource.options.schema.timezone;

                if (options.dataSource && timezone) {
                    var timezoneOffset = kendo.timezone.offset(currentTime, timezone);
                    currentTime = kendo.timezone.convert(currentTime, currentTime.getTimezoneOffset(), timezoneOffset);
                }
            }

            var groupsCount = !options.group || options.group.orientation == "vertical" ? 1 : this.groups.length;

            for (var groupIndex = 0; groupIndex < groupsCount; groupIndex++) {
                var currentGroup = this.groups[groupIndex];
                var utcCurrentTime = kendo.date.toUtcTime(currentTime);
                var ranges = currentGroup.timeSlotRanges(utcCurrentTime, utcCurrentTime + 1);
                if (ranges.length === 0) {
                    return;
                }
                var collection = ranges[0].collection;
                var slotElement = collection.slotByStartDate(currentTime);

                if (slotElement) {
                    var element = $("<div class='k-current-time'></div>");
                    var datesHeader = this.datesHeader;

                    element.appendTo(datesHeader.find(".k-scheduler-header-wrap")).css({
                        left: Math.round(ranges[0].innerRect(currentTime, new Date(currentTime.getTime() + 1), false).left),
                        width: "1px",
                        bottom: "1px",
                        top: 0
                    });
                }
            }
        },

        _currentTime: function () {
            var that = this;
            var markerOptions = that.options.currentTimeMarker;

            if (markerOptions !== false && markerOptions.updateInterval !== undefined) {
                var updateInterval = markerOptions.updateInterval;

                that._currentTimeMarkerUpdater();
                that._currentTimeUpdateTimer = setInterval(proxy(this._currentTimeMarkerUpdater, that), updateInterval);
            }
        },

        _editable: function () {
            if (this.options.editable) {
                if (this._isMobile()) {
                    this._touchEditable();
                } else {
                    this._mouseEditable();
                }
            }
        },

        _mouseEditable: function () {
            var that = this;
            that.element.on("click" + NS, ".k-event a:has(.k-si-close)", function (e) {
                that.trigger("remove", { uid: $(this).closest(".k-event").attr(kendo.attr("uid")) });
                e.preventDefault();
            });

            if (that.options.editable.create !== false) {
                that.element.on("dblclick" + NS, ".k-scheduler-content td", function (e) {
                    var slot = that._slotByPosition(e.pageX, e.pageY);

                    if (slot) {
                        var resourceInfo = that._resourceBySlot(slot);
                        that.trigger("add", { eventInfo: extend({ start: slot.startDate(), end: slot.endDate() }, resourceInfo) });
                    }

                    e.preventDefault();
                });
            }

            if (that.options.editable.update !== false) {
                that.element.on("dblclick" + NS, ".k-event", function (e) {
                    that.trigger("edit", { uid: $(this).closest(".k-event").attr(kendo.attr("uid")) });
                    e.preventDefault();
                });
            }
        },

        _touchEditable: function () {
            var that = this;

            if (that.options.editable.create !== false) {
                that._addUserEvents = new kendo.UserEvents(that.element, {
                    filter: ".k-scheduler-content td",
                    tap: function (e) {
                        var slot = that._slotByPosition(e.x.location, e.y.location);

                        if (slot) {
                            var resourceInfo = that._resourceBySlot(slot);
                            that.trigger("add", { eventInfo: extend({ start: slot.startDate(), end: slot.endDate() }, resourceInfo) });
                        }

                        e.preventDefault();
                    }
                });
            }

            if (that.options.editable.update !== false) {
                that._editUserEvents = new kendo.UserEvents(that.element, {
                    filter: ".k-event",
                    tap: function (e) {
                        var eventElement = $(e.target).closest(".k-event");

                        if (!eventElement.hasClass("k-event-active")) {
                            that.trigger("edit", { uid: eventElement.attr(kendo.attr("uid")) });
                        }

                        e.preventDefault();
                    }
                });
            }
        },

        _slotByPosition: function (x, y) {
            var slot;
            var content = this.content;
            var offset = content.offset();
            var group;
            var groupIndex;

            x -= offset.left;
            y -= offset.top;

            if (this._isRtl) {
                var browser = kendo.support.browser;

                if (browser.mozilla) {
                    x += (content[0].scrollWidth - content[0].offsetWidth);
                    x += content[0].scrollLeft;
                } else if (browser.msie) {
                    x -= content.scrollLeft();
                    x += content[0].scrollWidth - this.content[0].offsetWidth;
                } else if (browser.webkit) {
                    x += content[0].scrollLeft;
                }
            } else {
                x += content[0].scrollLeft;
            }

            y += content[0].scrollTop;

            x = Math.ceil(x);
            y = Math.ceil(y);

            for (groupIndex = 0; groupIndex < this.groups.length; groupIndex++) {
                group = this.groups[groupIndex];

                slot = group.timeSlotByPosition(x, y);

                if (slot) {
                    return slot;
                }
            }

            return null;
        },

        options: {
            name: "TimelineView",
            title: "Timeline",
            selectedDateFormat: "{0:D}",
            date: kendo.date.today(),
            startTime: kendo.date.today(),
            endTime: kendo.date.today(),
            showWorkHours: false,
            minorTickCount: 2,
            editable: true,
            workDayStart: new Date(1980, 1, 1, 8, 0, 0),
            workDayEnd: new Date(1980, 1, 1, 17, 0, 0),
            workWeekStart: 1,
            workWeekEnd: 5,
            majorTick: 60,
            eventHeight: 25,
            columnWidth: 100,
            groupHeaderTemplate: "#=text#",
            majorTimeHeaderTemplate: "#=kendo.toString(date, 't')#",
            slotTemplate: "&nbsp;",
            eventTemplate: EVENT_TEMPLATE,
            dateHeaderTemplate: DATA_HEADER_TEMPLATE,
            footer: {
                command: "workDay"
            },
            currentTimeMarker: {
                updateInterval: 10000,
                useLocalTimezone: true
            },
            messages: {
                defaultRowText: "All events",
                showFullDay: "Show full day",
                showWorkDay: "Show business hours"
            }
        },

        events: ["remove", "add", "edit"],

        _templates: function () {
            var options = this.options,
                settings = extend({}, kendo.Template, options.templateSettings);

            this.eventTemplate = this._eventTmpl(options.eventTemplate, EVENT_WRAPPER_STRING);
            this.majorTimeHeaderTemplate = kendo.template(options.majorTimeHeaderTemplate, settings);
            this.dateHeaderTemplate = kendo.template(options.dateHeaderTemplate, settings);
            this.slotTemplate = kendo.template(options.slotTemplate, settings);
            this.groupHeaderTemplate = kendo.template(options.groupHeaderTemplate, settings);
        },

        _render: function (dates) {
            var that = this;
            dates = dates || [];

            that._dates = dates;

            that._startDate = dates[0];

            that._endDate = dates[(dates.length - 1) || 0];

            that._calculateSlotRanges();

            that.createLayout(that._layout(dates));

            that._content(dates);

            that._footer();

            that._setContentWidth();

            that.refreshLayout();

            this.timesHeader.find("table tr:last").hide(); /*Chrome fix, use CSS selector*/
            this.datesHeader.find("table tr:last").hide();
        },

        _setContentWidth: function () {
            var content = this.content;
            var contentWidth = content.width();
            var contentTable = this.content.find("table");
            var columnCount = contentTable.find("tr:first").children().length;

            var minWidth = 100;
            var calculatedWidth = columnCount * this.options.columnWidth;

            if (contentWidth < calculatedWidth) {
                minWidth = Math.ceil((calculatedWidth / contentWidth) * 100);
            }

            contentTable.add(this.datesHeader.find("table"))
                .css("width", minWidth + "%");
        },

        _calculateSlotRanges: function () {
            var dates = this._dates;
            var slotStartTime = this.startTime();
            var slotEndTime = this.endTime();

            if (getMilliseconds(slotEndTime) === getMilliseconds(kendo.date.getDate(slotEndTime))) {
                slotEndTime = kendo.date.getDate(slotEndTime);
                setTime(slotEndTime, MS_PER_DAY - 1);
            }

            slotEndTime = getMilliseconds(slotEndTime);
            slotStartTime = getMilliseconds(slotStartTime);

            var slotRanges = [];
            for (var i = 0; i < dates.length; i++) {
                var rangeStart = getDate(dates[i]);
                setTime(rangeStart, slotStartTime);

                var rangeEnd = getDate(dates[i]);
                setTime(rangeEnd, slotEndTime);

                slotRanges.push({
                    start: kendo.date.toUtcTime(rangeStart),
                    end: kendo.date.toUtcTime(rangeEnd)
                });
            }

            this._slotRanges = slotRanges;
        },

        _forTimeRange: function (min, max, action, after) {
            min = toInvariantTime(min); //convert the date to 1/2/1980 and sets the time
            max = toInvariantTime(max);

            var that = this,
                msMin = getMilliseconds(min),
                msMax = getMilliseconds(max),
                minorTickCount = that.options.minorTickCount,
                msMajorInterval = that.options.majorTick * MS_PER_MINUTE,
                msInterval = msMajorInterval / minorTickCount || 1,
                start = new Date(+min),
                startDay = start.getDate(),
                msStart,
                idx = 0, length,
                html = "";

            length = MS_PER_DAY / msInterval;

            if (msMin != msMax) {
                if (msMin > msMax) {
                    msMax += MS_PER_DAY;
                }

                length = ((msMax - msMin) / msInterval);
            }

            length = Math.round(length);

            for (; idx < length; idx++) {
                var majorTickDivider = idx % (msMajorInterval / msInterval);
                var isMajorTickColumn = majorTickDivider === 0;
                var isMiddleColumn = majorTickDivider < minorTickCount - 1;
                var isLastSlotColumn = majorTickDivider === minorTickCount - 1;
                var minorTickColumns = minorTickCount;

                if (length % minorTickCount !== 0) {
                    var isLastMajorSlot = (length - (idx + 1)) < minorTickCount;
                    if (isMajorTickColumn && isLastMajorSlot) {
                        minorTickColumns = length % minorTickCount;
                    }
                }

                html += action(start, isMajorTickColumn, isMiddleColumn, isLastSlotColumn, minorTickColumns);

                setTime(start, msInterval, false);
            }

            if (msMax) {
                msStart = getMilliseconds(start);
                if (startDay < start.getDate()) {
                    msStart += MS_PER_DAY;
                }

                if (msStart > msMax) {
                    start = new Date(+max);
                }
            }

            if (after) {
                html += after(start);
            }

            return html;
        },

        _layout: function (dates) {
            var timeColumns = [];
            var columns = [];
            var that = this;
            var rows = [{ text: that.options.messages.defaultRowText}];

            var minorTickSlots = [];
            for (var minorTickIndex = 0; minorTickIndex < that.options.minorTickCount; minorTickIndex++) {
                minorTickSlots.push({
                    text: "",
                    className: ""
                });
            }

            this._forTimeRange(that.startTime(), that.endTime(), function (date, majorTick, middleColumn, lastSlotColumn, minorSlotsCount) {
                var template = that.majorTimeHeaderTemplate;

                if (majorTick) {
                    var timeColumn = {
                        text: template({ date: date }),
                        className: lastSlotColumn ? "k-slot-cell" : "",
                        columns: minorTickSlots.slice(0, minorSlotsCount)
                    };
                    setColspan(timeColumn);
                    timeColumns.push(timeColumn);
                }
            });

            for (var idx = 0; idx < dates.length; idx++) {
                columns.push({
                    text: kendo.format("{0:m}", dates[idx]),
                    className: "k-slot-cell",
                    columns: timeColumns.slice(0)
                });
            }

            var resources = this.groupedResources;
            if (resources.length) {
                if (this._groupOrientation() === "vertical") {
                    rows = that._createRowsLayout(resources, null, this.groupHeaderTemplate);
                } else {
                    columns = that._createColumnsLayout(resources, columns, this.groupHeaderTemplate);
                }
            }

            return {
                columns: columns,
                rows: rows
            };
        },

        _footer: function () {
            var options = this.options;

            if (options.footer !== false) {
                var html = '<div class="k-header k-scheduler-footer">';

                var command = options.footer.command;

                if (command && command === "workDay") {
                    html += '<ul class="k-reset k-header">';

                    html += '<li class="k-state-default k-scheduler-fullday"><a href="#" class="k-link"><span class="k-icon k-i-clock"></span>';
                    html += (options.showWorkHours ? options.messages.showFullDay : options.messages.showWorkDay) + '</a></li>';

                    html += '</ul>';

                } else {
                    html += "&nbsp;";
                }

                html += "</div>";

                this.footer = $(html).appendTo(this.element);

                var that = this;

                this.footer.on("click" + NS, ".k-scheduler-fullday", function (e) {
                    e.preventDefault();
                    that.trigger("navigate", { view: that.name || options.name, date: that.startDate(), isWorkDay: !options.showWorkHours });
                });
            }
        },

        _columnCountForLevel: function (level) {
            var columnLevel = this.columnLevels[level];
            return columnLevel ? columnLevel.length : 0;
        },

        _rowCountForLevel: function (level) {
            var rowLevel = this.rowLevels[level];
            return rowLevel ? rowLevel.length : 0;
        },

        _isWorkDay: function (date) {
            var day = date.getDay();
            var workDays = this._workDays;

            for (var i = 0; i < workDays.length; i++) {
                if (workDays[i] === day) {
                    return true;
                }
            }
            return false;
        },

        _content: function (dates) {
            var that = this;
            var options = that.options;
            var start = that.startTime();
            var end = this.endTime();
            var groupsCount = 1;
            var rowCount = 1;
            var columnCount = dates.length;
            var html = '';
            var resources = this.groupedResources;
            var slotTemplate = this.slotTemplate;
            var isVerticalGrouped = false;

            if (resources.length) {
                isVerticalGrouped = that._groupOrientation() === "vertical";

                if (isVerticalGrouped) {
                    rowCount = that._groupCount();
                } else {
                    groupsCount = that._groupCount();
                }
            }

            html += '<tbody>';

            var appendRow = function (date, majorTick) {
                var content = "";
                var classes = "";
                var tmplDate;

                var resources = function (groupIndex) {
                    return function () {
                        return that._resourceBySlot({ groupIndex: groupIndex });
                    };
                };

                if (kendo.date.isToday(dates[idx])) {
                    classes += "k-today";
                }

                if (kendo.date.getMilliseconds(date) < kendo.date.getMilliseconds(options.workDayStart) ||
                    kendo.date.getMilliseconds(date) >= kendo.date.getMilliseconds(options.workDayEnd) ||
                    !that._isWorkDay(dates[idx])) {
                    classes += " k-nonwork-hour";
                }

                content += '<td' + (classes !== "" ? ' class="' + classes + '"' : "") + ">";
                tmplDate = kendo.date.getDate(dates[idx]);
                kendo.date.setTime(tmplDate, kendo.date.getMilliseconds(date));

                content += slotTemplate({ date: tmplDate, resources: resources(isVerticalGrouped ? rowIdx : groupIdx) });
                content += "</td>";

                return content;
            };


            for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
                html += '<tr>';

                for (var groupIdx = 0; groupIdx < groupsCount; groupIdx++) {
                    for (var idx = 0, length = columnCount; idx < length; idx++) {
                        html += this._forTimeRange(start, end, appendRow);
                    }
                }

                html += "</tr>";
            }

            html += '</tbody>';

            this.content.find("table").append(html);
        },

        _groups: function () {
            var groupCount = this._groupCount();
            var dates = this._dates;
            var columnCount = dates.length;

            this.groups = [];

            for (var idx = 0; idx < groupCount; idx++) {
                var view = this._addResourceView(idx);

                var start = dates[0];
                var end = dates[(dates.length - 1) || 0];
                view.addTimeSlotCollection(start, kendo.date.addDays(end, 1));
            }

            this._timeSlotGroups(groupCount, columnCount);
        },

        _isVerticallyGrouped: function () {
            return this.groupedResources.length && this._groupOrientation() === "vertical";
        },

        _isHorizontallyGrouped: function () {
            return this.groupedResources.length && this._groupOrientation() === "horizontal";
        },

        _timeSlotGroups: function (groupCount, datesCount) {
            var interval = this._timeSlotInterval();
            var isVerticallyGrouped = this._isVerticallyGrouped();
            var tableRows = this.content.find("tr");
            var rowCount = tableRows.length;

            tableRows.attr("role", "row");

            if (isVerticallyGrouped) {
                rowCount = Math.floor(rowCount / groupCount);
            }

            for (var groupIndex = 0; groupIndex < groupCount; groupIndex++) {
                var rowMultiplier = 0;
                var group = this.groups[groupIndex];
                var time;

                if (isVerticallyGrouped) {
                    rowMultiplier = groupIndex;
                }

                var rowIndex = rowMultiplier * rowCount;
                var cellMultiplier = 0;

                if (!isVerticallyGrouped) {
                    cellMultiplier = groupIndex;
                }

                var cells = tableRows[rowIndex].children;
                var cellsPerGroup = cells.length / (!isVerticallyGrouped ? groupCount : 1);
                var cellsPerDay = cellsPerGroup / datesCount;

                for (var dateIndex = 0; dateIndex < datesCount; dateIndex++) {
                    var cellOffset = dateIndex * cellsPerDay + (cellsPerGroup * cellMultiplier);
                    time = getMilliseconds(new Date(+this.startTime()));

                    for (var cellIndex = 0; cellIndex < cellsPerDay; cellIndex++) {
                        var cell = cells[cellIndex + cellOffset];
                        var collection = group.getTimeSlotCollection(0);
                        var currentDate = this._dates[dateIndex];
                        var currentTime = Date.UTC(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate());
                        var start = currentTime + time;
                        var end = start + interval;
                        cell.setAttribute("role", "gridcell");
                        cell.setAttribute("aria-selected", false);

                        collection.addTimeSlot(cell, start, end, true);
                        time += interval;
                    }
                }
            }
        },

        startDate: function () {
            return this._startDate;
        },

        endDate: function () {
            return this._endDate;
        },

        startTime: function () {
            var options = this.options;
            return options.showWorkHours ? options.workDayStart : options.startTime;
        },

        endTime: function () {
            var options = this.options;
            return options.showWorkHours ? options.workDayEnd : options.endTime;
        },

        _timeSlotInterval: function () {
            var options = this.options;
            return (options.majorTick / options.minorTickCount) * MS_PER_MINUTE;
        },

        nextDate: function () {
            return kendo.date.nextDay(this.endDate());
        },
        previousDate: function () {
            return kendo.date.previousDay(this.startDate());
        },

        calculateDateRange: function () {
            this._render([this.options.date]);
        },

        render: function (events) {
            this._headerColumnCount = 0;

            this._groups();

            this.element.find(".k-event").remove();

            events = new kendo.data.Query(events)
                .sort([{ field: "start", dir: "asc" }, { field: "end", dir: "desc"}])
                .toArray();

            var eventsByResource = [];

            this._eventsByResource(events, this.groupedResources, eventsByResource);

            var eventsPerDate = $.map(this._dates, function (date) {
                return Math.max.apply(null,
                    $.map(eventsByResource, function (events) {
                        return $.grep(events, function (event) {
                            return isInDateRange(date, getDate(event.start), getDate(event.end));
                        }).length;
                    })
                );
            });

            var eventGroups = [];
            var maxRowCount = 0;

            for (var groupIndex = 0; groupIndex < eventsByResource.length; groupIndex++) {
                var eventGroup = {
                    groupIndex: groupIndex,
                    maxRowCount: 0,
                    events: {}
                };

                eventGroups.push(eventGroup);

                this._renderEvents(eventsByResource[groupIndex], groupIndex, eventGroup);

                if (maxRowCount < eventGroup.maxRowCount) {
                    maxRowCount = eventGroup.maxRowCount;
                }
            }

            this._setRowsHeight(eventGroups, eventsByResource.length, maxRowCount);

            this._positionEvents(eventGroups, eventsByResource.length);

            this.trigger("activate");
        },

        _positionEvents: function (eventGroups, groupsCount) {
            for (var groupIndex = 0; groupIndex < groupsCount; groupIndex++) {
                var eventsForGroup = eventGroups[groupIndex].events;
                for (var eventUid in eventsForGroup) {
                    var eventObject = eventsForGroup[eventUid];
                    this._positionEvent(eventObject);
                }
            }
        },

        _setRowsHeight: function (eventGroups, groupsCount, maxRowCount) {
            var eventHeight = this.options.eventHeight + 2; /* two times border width */
            var eventBottomOffset = this._getBottomRowOffset();

            groupsCount = this._isVerticallyGrouped() ? groupsCount : 1;
            for (var groupIndex = 0; groupIndex < groupsCount; groupIndex++) {
                var rowsCount = this._isVerticallyGrouped() ? eventGroups[groupIndex].maxRowCount : maxRowCount;

                rowsCount = rowsCount ? rowsCount : 1;

                var rowHeight = ((eventHeight + 2) * rowsCount) + eventBottomOffset;
                var timesRow = this.times.find(kendo.format("tr:nth-child({0})", groupIndex + 1));
                var row = this.content.find(kendo.format("tr:nth-child({0})", groupIndex + 1));

                timesRow.height(rowHeight);
                row.height(rowHeight);
            }


            this._setContentWidth();
            this.refreshLayout();
            this._refreshSlots();
        },

        _getBottomRowOffset: function () {
            var eventBottomOffset = this.options.eventHeight * 0.50;
            var isMobile = this._isMobile();
            var minOffset;
            var maxOffset;

            if (isMobile) {
                minOffset = 30;
                maxOffset = 60;
            } else {
                minOffset = 15;
                maxOffset = 30;
            }

            if (eventBottomOffset > maxOffset) {
                eventBottomOffset = maxOffset;
            } else if (eventBottomOffset < minOffset) {
                eventBottomOffset = minOffset;
            }

            return eventBottomOffset;
        },

        _positionEvent: function (eventObject) {
            var eventHeight = this.options.eventHeight + 2;

            var rect = eventObject.slotRange.innerRect(eventObject.start, eventObject.end, false);

            rect.top = eventObject.slotRange.start.offsetTop;
            var width = rect.right - rect.left - 2;

            if (width < 0) {
                width = 0;
            }

            var left = rect.left;
            if (this._isRtl) {
                left -= (this.content[0].scrollWidth - this.content[0].offsetWidth);
            }

            eventObject.element.css({
                top: eventObject.slotRange.start.offsetTop + eventObject.rowIndex * (eventHeight + 2) + "px",
                left: left,
                width: width
            });
        },

        _refreshSlots: function () {
            for (var groupIndex = 0; groupIndex < this.groups.length; groupIndex++) {
                this.groups[groupIndex].refresh();
            }
        },

        _eventsByResource: function (events, resources, result) {
            var resource = resources[0];

            if (resource) {
                var view = resource.dataSource.view();

                for (var itemIdx = 0; itemIdx < view.length; itemIdx++) {
                    var value = this._resourceValue(resource, view[itemIdx]);

                    var eventsFilteredByResource = new kendo.data.Query(events).filter({ field: resource.field, operator: SchedulerView.groupEqFilter(value) }).toArray();

                    if (resources.length > 1) {
                        this._eventsByResource(eventsFilteredByResource, resources.slice(1), result);
                    } else {
                        result.push(eventsFilteredByResource);
                    }
                }
            } else {
                result.push(events);
            }
        },

        _isInDateSlot: function (event) {
            var startTime = event.start;
            var endTime = event.end;
            var rangeStart = getDate(this._startDate);
            var rangeEnd = kendo.date.addDays(getDate(this._endDate), 1);

            if (startTime < rangeEnd && rangeStart <= endTime) {
                return true;
            }
            return false;
        },

        _isInTimeSlot: function (event) {
            var startTime = event.startTime || kendo.date.toUtcTime(event.start);
            var endTime = event.endTime || kendo.date.toUtcTime(event.end);

            var slotRanges = this._slotRanges;

            if (startTime === endTime) {
                endTime = endTime + 1;
            }

            for (var slotIndex = 0; slotIndex < slotRanges.length; slotIndex++) {
                if (startTime < slotRanges[slotIndex].end && slotRanges[slotIndex].start < endTime) {
                    return true;
                }
            }
            return false;
        },

        _adjustEvent: function (event) {
            var start = event.start;
            var end = event.end;
            var eventStartTime = event._time("start");
            var eventEndTime = event._time("end");
            var startTime = getMilliseconds(this.startTime());
            var endTime = getMilliseconds(this.endTime());
            var adjustedStartDate = null;
            var adjustedEndDate = null;
            var occurrence;
            var head = false;
            var tail = false;

            if (event.isAllDay) {
                adjustedStartDate = getDate(start);
                if (startTime > eventStartTime) {
                    setTime(adjustedStartDate, startTime);
                    tail = true;
                }

                adjustedEndDate = getDate(end);
                if (endTime === getMilliseconds(getDate(this.endTime()))) {
                    adjustedEndDate = kendo.date.addDays(adjustedEndDate, 1);
                } else {
                    setTime(adjustedEndDate, endTime);
                    head = true;
                }
            } else {
                endTime = endTime === 0 ? MS_PER_DAY : endTime;

                if (startTime > eventStartTime) {
                    adjustedStartDate = getDate(start);
                    setTime(adjustedStartDate, startTime);
                    tail = true;
                } else if (endTime < eventStartTime) {
                    adjustedStartDate = getDate(start);
                    adjustedStartDate = kendo.date.addDays(adjustedStartDate, 1);
                    setTime(adjustedStartDate, startTime);
                    tail = true;
                }

                if (endTime < eventEndTime) {
                    adjustedEndDate = getDate(end);
                    setTime(adjustedEndDate, endTime);
                    head = true;
                } else if (startTime > eventEndTime) {
                    adjustedEndDate = getDate(end);
                    adjustedEndDate = kendo.date.addDays(adjustedEndDate, -1);
                    setTime(adjustedEndDate, endTime);
                    head = true;
                }
            }

            occurrence = event.clone({
                start: adjustedStartDate ? adjustedStartDate : start,
                end: adjustedEndDate ? adjustedEndDate : end,
                startTime: adjustedStartDate ? kendo.date.toUtcTime(adjustedStartDate) : event.startTime,
                endTime: adjustedEndDate ? kendo.date.toUtcTime(adjustedEndDate) : event.endTime,
                isAllDay: false
            });

            return {
                occurrence: occurrence,
                head: head,
                tail: tail
            };
        },

        _renderEvents: function (events, groupIndex, eventGroup) {
            var event;
            var idx;
            var length;

            for (idx = 0, length = events.length; idx < length; idx++) {
                event = events[idx];

                if (this._isInDateSlot(event)) {
                    var isMultiDayEvent = event.isAllDay || event.end.getTime() - event.start.getTime() >= MS_PER_DAY;
                    var container = this.content;

                    if (isMultiDayEvent || this._isInTimeSlot(event)) {
                        var adjustedEvent = this._adjustEvent(event);
                        var group = this.groups[groupIndex];

                        if (!group._continuousEvents) {
                            group._continuousEvents = [];
                        }

                        var ranges = group.slotRanges(adjustedEvent.occurrence, false);
                        var range = ranges[0];
                        var element;

                        if (this._isInTimeSlot(adjustedEvent.occurrence)) {

                            element = this._createEventElement(adjustedEvent.occurrence, event, range.head || adjustedEvent.head, range.tail || adjustedEvent.tail);
                            element.appendTo(container).css({ top: 0, height: this.options.eventHeight });

                            var eventObject = {
                                start: adjustedEvent.occurrence.startTime || adjustedEvent.occurrence.start,
                                end: adjustedEvent.occurrence.endTime || adjustedEvent.occurrence.end,
                                element: element,
                                uid: event.uid,
                                slotRange: range,
                                rowIndex: 0,
                                offsetTop: 0
                            };

                            eventGroup.events[event.uid] = eventObject;

                            this.addContinuousEvent(group, range, element, event.isAllDay);
                            this._arrangeRows(eventObject, range, eventGroup);
                        }
                    }
                }
            }
        },

        addContinuousEvent: function (group, range, element, isAllDay) {
            var events = group._continuousEvents;

            events.push({
                element: element,
                isAllDay: isAllDay,
                uid: element.attr(kendo.attr("uid")),
                start: range.start,
                end: range.end
            });
        },

        _createEventElement: function (occurrence, event, head, tail) {
            var template = this.eventTemplate;
            var editable = this.options.editable;
            var isMobile = this._isMobile();
            var showDelete = editable && editable.destroy !== false && !isMobile;
            var resizable = editable && editable.resize !== false;
            var eventStartTime = event._time("start");
            var eventEndTime = event._time("end");
            var eventStartDate = event.start;
            var eventEndDate = event.end;

            var resources = this.eventResources(event);

            if (event.startTime) {
                eventStartDate = new Date(eventStartTime);
                eventStartDate = kendo.timezone.apply(eventStartDate, "Etc/UTC");
            }

            if (event.endTime) {
                eventEndDate = new Date(eventEndTime);
                eventEndDate = kendo.timezone.apply(eventEndDate, "Etc/UTC");
            }

            var data = extend({}, {
                ns: kendo.ns,
                resizable: resizable,
                showDelete: showDelete,
                head: head,
                tail: tail,
                singleDay: this._dates.length == 1,
                resources: resources,
                inverseColor: resources && resources[0] ? this._shouldInverseResourceColor(resources[0]) : false
            }, event, {
                start: eventStartDate,
                end: eventEndDate
            });

            var element = $(template(data));

            this.angular("compile", function () {
                return {
                    elements: element,
                    data: [{ dataItem: data}]
                };
            });

            return element;
        },

        _arrangeRows: function (eventObject, slotRange, eventGroup) {
            var startIndex = slotRange.start.index;
            var endIndex = slotRange.end.index;
            var events = SchedulerView.collidingEvents(slotRange.events(), startIndex, endIndex);

            slotRange.addEvent({ slotIndex: startIndex, start: startIndex, end: endIndex, element: eventObject.element, uid: eventObject.uid });
            events.push({ slotIndex: startIndex, start: startIndex, end: endIndex, element: eventObject.element, uid: eventObject.uid });

            var rows = SchedulerView.createRows(events);

            if (eventGroup.maxRowCount < rows.length) {
                eventGroup.maxRowCount = rows.length;
            }

            for (var idx = 0, length = rows.length; idx < length; idx++) {
                var rowEvents = rows[idx].events;
                for (var j = 0, eventLength = rowEvents.length; j < eventLength; j++) {
                    eventGroup.events[rowEvents[j].uid].rowIndex = idx;
                }
            }
        },

        _groupCount: function () {
            var resources = this.groupedResources;

            if (resources.length) {
                if (this._groupOrientation() === "vertical") {
                    return this._rowCountForLevel(resources.length - 1);
                } else {
                    return this._columnCountForLevel(resources.length - 1);
                }
            }
            return 1;
        },

        _updateEventForSelection: function (event) {
            var adjustedEvent = this._adjustEvent(event.clone());
            return adjustedEvent.occurrence;
        },

        _updateEventForMove: function (event) {
            if (event.isAllDay) {
                event.set("isAllDay", false);
            }
        },

        _updateEventForResize: function (event) {
            if (event.isAllDay) {
                event.set("isAllDay", false);
            }
        },

        _updateMoveHint: function (event, groupIndex, distance) {
            var group = this.groups[groupIndex];

            var clonedEvent = event.clone({ start: event.start, end: event.end });

            var eventDuraton = clonedEvent.duration();
            clonedEvent.start = new Date(clonedEvent.start.getTime() + distance);
            clonedEvent.end = new Date(+clonedEvent.start + eventDuraton);

            var adjustedEvent = this._adjustEvent(clonedEvent);

            var ranges = group.slotRanges(adjustedEvent.occurrence, false);

            this._removeMoveHint();

            for (var rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) {
                var range = ranges[rangeIndex];
                var startSlot = range.start;

                var hint = this._createEventElement(adjustedEvent.occurrence, adjustedEvent.occurrence, false, false);

                hint.addClass("k-event-drag-hint");

                var rect = range.innerRect(adjustedEvent.occurrence.start, adjustedEvent.occurrence.end, this.options.snap);
                var width = rect.right - rect.left - 2;

                if (width < 0) {
                    width = 0;
                }

                var left = rect.left;

                if (this._isRtl) {
                    left -= (this.content[0].scrollWidth - this.content[0].offsetWidth);
                }

                var css = {
                    left: left,
                    top: startSlot.offsetTop,
                    height: startSlot.offsetHeight - 2,
                    width: width
                };

                hint.css(css);

                this._moveHint = this._moveHint.add(hint);
            }

            var content = this.content;

            this._moveHint.appendTo(content);
        },

        _updateResizeHint: function (event, groupIndex, startTime, endTime) {
            var group = this.groups[groupIndex];
            var ranges = group.ranges(startTime, endTime, false, false);

            this._removeResizeHint();

            for (var rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) {
                var range = ranges[rangeIndex];
                var start = range.startSlot();

                var startRect = range.innerRect(startTime, endTime, false);
                startRect.top = start.offsetTop;

                var width = startRect.right - startRect.left;
                var height = start.offsetHeight;

                var left = startRect.left;

                var content = this.content;
                if (this._isRtl) {
                    left -= (content[0].scrollWidth - content[0].offsetWidth);
                }

                var hint = SchedulerView.fn._createResizeHint.call(this,
                    left,
                    startRect.top,
                    width,
                    height
                );

                this._resizeHint = this._resizeHint.add(hint);
            }

            var format = "t";
            var container = this.content;

            this._resizeHint.appendTo(container);

            this._resizeHint.find(".k-label-top,.k-label-bottom").text("");

            this._resizeHint.first().addClass("k-first").find(".k-label-top").text(kendo.toString(kendo.timezone.toLocalDate(startTime), format));

            this._resizeHint.last().addClass("k-last").find(".k-label-bottom").text(kendo.toString(kendo.timezone.toLocalDate(endTime), format));

        },

        selectionByElement: function (cell) {
            var offset = cell.offset();
            return this._slotByPosition(offset.left, offset.top);
        },

        _updateDirection: function (selection, ranges, multiple, reverse, vertical) {

            var startSlot = ranges[0].start;
            var endSlot = ranges[ranges.length - 1].end;
            if (multiple && !vertical) {
                if (startSlot.index === endSlot.index &&
                    startSlot.collectionIndex === endSlot.collectionIndex) {
                    selection.backward = reverse;
                }
            }
        },

        _changeGroup: function (selection, previous) {
            var method = previous ? "prevGroupSlot" : "nextGroupSlot";

            var slot = this[method](selection.start, selection.groupIndex, false);

            if (slot) {
                selection.groupIndex += previous ? -1 : 1;
            }

            return slot;
        },

        prevGroupSlot: function (date, groupIndex, isDay) {
            var group = this.groups[groupIndex];
            var slot = group.ranges(date, date, isDay, false)[0].start;

            if (groupIndex <= 0) {
                return;
            }

            if (this._isVerticallyGrouped()) {
                return slot;
            } else {
                var collection = group._collection(0, isDay);
                return collection.last();
            }
        },

        nextGroupSlot: function (date, groupIndex, isDay) {
            var group = this.groups[groupIndex];
            var slot = group.ranges(date, date, isDay, false)[0].start;

            if (groupIndex >= this.groups.length - 1) {
                return;
            }

            if (this._isVerticallyGrouped()) {
                return slot;
            } else {
                var collection = group._collection(0, isDay);
                return collection.first();
            }
        },

        _verticalSlots: function (selection, ranges, multiple, reverse) {
            var method = reverse ? "leftSlot" : "rightSlot";
            var startSlot = ranges[0].start;
            var endSlot = ranges[ranges.length - 1].end;
            var group = this.groups[selection.groupIndex];

            startSlot = group[method](startSlot);
            endSlot = group[method](endSlot);

            if (!multiple && this._isVerticallyGrouped() && (!startSlot || !endSlot)) {
                startSlot = endSlot = this._changeGroup(selection, reverse);
            }

            return {
                startSlot: startSlot,
                endSlot: endSlot
            };
        },

        _horizontalSlots: function (selection, ranges, multiple, reverse) {
            var method = reverse ? "upSlot" : "downSlot";
            var startSlot = ranges[0].start;
            var endSlot = ranges[ranges.length - 1].end;
            var group = this.groups[selection.groupIndex];

            startSlot = group[method](startSlot);
            endSlot = group[method](endSlot);

            if (!multiple && this._isHorizontallyGrouped() && (!startSlot || !endSlot)) {
                startSlot = endSlot = this._changeGroup(selection, reverse);
            }

            return {
                startSlot: startSlot,
                endSlot: endSlot
            };
        },

        _changeViewPeriod: function (selection, reverse, vertical) {
            var date = reverse ? this.previousDate() : this.nextDate();
            var start = selection.start;
            var end = selection.end;

            selection.start = new Date(date);
            selection.end = new Date(date);

            if (this._isHorizontallyGrouped()) {
                selection.groupIndex = reverse ? this.groups.length - 1 : 0;
            }

            var duration = end - start;

            if (reverse) {
                end = getMilliseconds(this.endTime());
                end = end === 0 ? MS_PER_DAY : end;

                setTime(selection.start, end - duration);
                setTime(selection.end, end);
            } else {
                start = getMilliseconds(this.startTime());

                setTime(selection.start, start);
                setTime(selection.end, start + duration);
            }

            selection.events = [];

            return true;
        },

        move: function (selection, key, shift) {
            var handled = false;
            var group = this.groups[selection.groupIndex];
            var keys = kendo.keys;

            var ranges = group.ranges(selection.start, selection.end, false, false);
            var startSlot, endSlot, reverse, slots;

            if (key === keys.DOWN || key === keys.UP) {
                handled = true;
                reverse = key === keys.UP;

                this._updateDirection(selection, ranges, shift, reverse, true);

                slots = this._verticalSlots(selection, ranges, shift, reverse);
            } else if (key === keys.LEFT || key === keys.RIGHT) {
                handled = true;
                reverse = key === keys.LEFT;

                this._updateDirection(selection, ranges, shift, reverse, false);

                slots = this._horizontalSlots(selection, ranges, shift, reverse);

                if ((!slots.startSlot || !slots.endSlot) && !shift && this._changeViewPeriod(selection, reverse, false)) {
                    return handled;
                }
            }

            if (handled) {
                startSlot = slots.startSlot;
                endSlot = slots.endSlot;

                if (shift) {
                    var backward = selection.backward;

                    if (backward && startSlot) {
                        selection.start = startSlot.startDate();
                    } else if (!backward && endSlot) {
                        selection.end = endSlot.endDate();
                    }
                } else if (startSlot && endSlot) {
                    selection.start = startSlot.startDate();
                    selection.end = endSlot.endDate();
                }

                selection.events = [];
            }

            return handled;
        },

        destroy: function () {
            var that = this;

            if (that.element) {
                that.element.off(NS);
            }

            if (that.footer) {
                that.footer.remove();
            }

            if (that._currentTimeUpdateTimer) {
                clearInterval(that._currentTimeUpdateTimer);
            }

            SchedulerView.fn.destroy.call(this);

            if (this._isMobile() && that.options.editable) {
                if (that.options.editable.create !== false) {
                    that._addUserEvents.destroy();
                    that._allDayUserEvents.destroy();
                }

                if (that.options.editable.update !== false) {
                    that._editUserEvents.destroy();
                }
            }
        }
    });

    extend(true, ui, {
        TimelineView: TimelineView,
        TimelineWeekView: TimelineView.extend({
            options: {
                name: "TimelineWeekView",
                title: "Timeline Week",
                selectedDateFormat: "{0:D} - {1:D}",
                majorTick: 120
            },
            name: "timelineWeek",
            calculateDateRange: function () {
                var selectedDate = this.options.date,
                    start = kendo.date.dayOfWeek(selectedDate, this.calendarInfo().firstDay, -1),
                    idx, length,
                    dates = [];

                for (idx = 0, length = 7; idx < length; idx++) {
                    dates.push(start);
                    start = kendo.date.nextDay(start);
                }
                this._render(dates);
            }
        }),
        TimelineWorkWeekView: TimelineView.extend({
            options: {
                name: "TimelineWorkWeekView",
                title: "Timeline Work Week",
                selectedDateFormat: "{0:D} - {1:D}",
                majorTick: 120
            },
            name: "timelineWorkWeek",
            nextDate: function () {
                return kendo.date.dayOfWeek(kendo.date.nextDay(this.endDate()), this.options.workWeekStart, 1);
            },
            previousDate: function () {
                return kendo.date.previousDay(this.startDate());
            },
            calculateDateRange: function () {
                var selectedDate = this.options.date,
                    start = kendo.date.dayOfWeek(selectedDate, this.options.workWeekStart, -1),
                    end = kendo.date.dayOfWeek(start, this.options.workWeekEnd, 1),
                    dates = [];

                while (start <= end) {
                    dates.push(start);
                    start = kendo.date.nextDay(start);
                }
                this._render(dates);
            }
        }),
        TimelineMonthView: TimelineView.extend({
            options: {
                name: "TimelineMonthView",
                title: "Timeline Month",
                selectedDateFormat: "{0:D} - {1:D}",
                workDayStart: new Date(1980, 1, 1, 0, 0, 0),
                workDayEnd: new Date(1980, 1, 1, 0, 0, 0),
                footer: false,
                majorTick: 1440,
                minorTickCount: 1
            },
            name: "timelineWeek",
            calculateDateRange: function () {
                var selectedDate = this.options.date,
                    start = kendo.date.firstDayOfMonth(selectedDate),
                    end = kendo.date.lastDayOfMonth(selectedDate),
                    idx, length,
                    dates = [];

                for (idx = 0, length = end.getDate(); idx < length; idx++) {
                    dates.push(start);
                    start = kendo.date.nextDay(start);
                }
                this._render(dates);
            }
        })
    });

})(window.kendo.jQuery);





/*jshint eqnull: true */
(function ($, undefined) {
    var kendo = window.kendo,
        date = kendo.date,
        input_support = kendo.support.input,
        MS_PER_DAY = date.MS_PER_DAY,
        getDate = date.getDate,
        getMilliseconds = kendo.date.getMilliseconds,
        recurrence = kendo.recurrence,
        keys = kendo.keys,
        ui = kendo.ui,
        Widget = ui.Widget,
        STRING = "string",
        Popup = ui.Popup,
        Calendar = ui.Calendar,
        DataSource = kendo.data.DataSource,
        isPlainObject = $.isPlainObject,
        extend = $.extend,
        proxy = $.proxy,
        toString = Object.prototype.toString,
        isArray = $.isArray,
        NS = ".kendoScheduler",
        CLICK = "click",
        CHANGE = "change",
        CANCEL = "cancel",
        REMOVE = "remove",
        SAVE = "save",
        ADD = "add",
        EDIT = "edit",
        valueStartEndBoundRegex = /(?:value:start|value:end)(?:,|$)/,
        TODAY = getDate(new Date()),
        RECURRENCE_EXCEPTION = "recurrenceException",
        DELETECONFIRM = "Are you sure you want to delete this event?",
        DELETERECURRING = "Do you want to delete only this event occurrence or the whole series?",
        EDITRECURRING = "Do you want to edit only this event occurrence or the whole series?",
        COMMANDBUTTONTMPL = '<a class="k-button #=className#" #=attr# href="\\#">#=text#</a>',
        TOOLBARTEMPLATE = kendo.template('<div class="k-floatwrap k-header k-scheduler-toolbar">' +
           '# if (pdf) { #' +
           '<ul class="k-reset k-scheduler-tools">' +
               '<li><a role="button" href="\\#" class="k-button k-pdf"><span class="k-icon k-i-pdf"></span>${messages.pdf}</a></li>' +
           '</ul>' +
           '# } #' +
            '<ul class="k-reset k-scheduler-navigation">' +
               '<li class="k-state-default k-header k-nav-today"><a role="button" href="\\#" class="k-link">${messages.today}</a></li>' +
               '<li class="k-state-default k-header k-nav-prev"><a role="button" href="\\#" class="k-link"><span class="k-icon k-i-arrow-w"></span></a></li>' +
               '<li class="k-state-default k-header k-nav-next"><a role="button" href="\\#" class="k-link"><span class="k-icon k-i-arrow-e"></span></a></li>' +
               '<li class="k-state-default k-nav-current"><a role="button" href="\\#" class="k-link"><span class="k-icon k-i-calendar"></span><span data-#=ns#bind="text: formattedDate"></span></a></li>' +
            '</ul>' +
            '<ul class="k-reset k-header k-scheduler-views">' +
                '#for(var view in views){#' +
                    '<li class="k-state-default k-view-#= view.toLowerCase() #" data-#=ns#name="#=view#"><a role="button" href="\\#" class="k-link">${views[view].title}</a></li>' +
                '#}#' +
            '</ul>' +
            '</div>'),
        MOBILETOOLBARTEMPLATE = kendo.template('<div class="k-floatwrap k-header k-scheduler-toolbar">' +
            '<ul class="k-reset k-header k-scheduler-navigation">' +
               '<li class="k-state-default k-nav-today"><a role="button" href="\\#" class="k-link">${messages.today}</a></li>' +
            '</ul>' +
            '<ul class="k-reset k-header k-scheduler-views">' +
                '#for(var view in views){#' +
                    '<li class="k-state-default k-view-#= view.toLowerCase() #" data-#=ns#name="#=view#"><a role="button" href="\\#" class="k-link">${views[view].title}</a></li>' +
                '#}#' +
            '</ul>' +
            '</div>' +
            '<div class="k-floatwrap k-header k-scheduler-toolbar">' +
                '<ul class="k-reset k-header k-scheduler-navigation">' +
                   '<li class="k-state-default k-nav-prev"><a role="button" href="\\#" class="k-link"><span class="k-icon k-i-arrow-w"></span></a></li>' +
                   '<li class="k-state-default k-nav-current"><span data-#=ns#bind="text: formattedDate"></span></li>' +
                   '<li class="k-state-default k-nav-next"><a role="button" href="\\#" class="k-link"><span class="k-icon k-i-arrow-e"></span></a></li>' +
                '</ul>' +
            '</div>'),
        MOBILEDATERANGEEDITOR = function (container, options) {
            var attr = { name: options.field };
            var datepicker_role = !input_support.date ? kendo.attr("role") + '="datepicker" ' : "";
            var datetimepicker_role = kendo.attr("role") + '="datetimepicker" ';
            var isAllDay = options.model.isAllDay;
            var dateTimeValidate = kendo.attr("validate") + "='" + (!isAllDay) + "'";
            var dateValidate = kendo.attr("validate") + "='" + isAllDay + "'";

            appendTimezoneAttr(attr, options);
            appendDateCompareValidator(attr, options);

            $('<input type="datetime-local" required ' + kendo.attr("type") + '="date" ' + datetimepicker_role + kendo.attr("bind") + '="value:' + options.field + ',invisible:isAllDay" ' +
                dateTimeValidate + '/>')
                .attr(attr).appendTo(container);

            $('<input type="date" required ' + kendo.attr("type") + '="date" ' + datepicker_role + kendo.attr("bind") + '="value:' + options.field + ',visible:isAllDay" ' +
                dateValidate + '/>')
                .attr(attr).appendTo(container);

            $('<span ' + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
        },
        DATERANGEEDITOR = function (container, options) {
            var attr = { name: options.field },
                isAllDay = options.model.isAllDay,
                dateTimeValidate = kendo.attr("validate") + "='" + (!isAllDay) + "' ",
                dateValidate = kendo.attr("validate") + "='" + isAllDay + "' ";

            appendTimezoneAttr(attr, options);
            appendDateCompareValidator(attr, options);

            $('<input type="text" required ' + kendo.attr("type") + '="date"' + ' ' + kendo.attr("role") + '="datetimepicker" ' + kendo.attr("bind") + '="value:' + options.field + ',invisible:isAllDay" ' +
                dateTimeValidate + '/>')
            .attr(attr).appendTo(container);

            $('<input type="text" required ' + kendo.attr("type") + '="date"' + ' ' + kendo.attr("role") + '="datepicker" ' + kendo.attr("bind") + '="value:' + options.field + ',visible:isAllDay" ' +
                dateValidate + '/>')
            .attr(attr).appendTo(container);

            $('<span ' + kendo.attr("bind") + '="text: ' + options.field + 'Timezone"></span>').appendTo(container);

            if (options.field === "end") {
                $('<span ' + kendo.attr("bind") + '="text: startTimezone, invisible: endTimezone"></span>').appendTo(container);
            }

            $('<span ' + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
        },
        RECURRENCEEDITOR = function (container, options) {
            $('<div ' + kendo.attr("bind") + '="value:' + options.field + '" />')
                .attr({
                    name: options.field
                })
                .appendTo(container)
                .kendoRecurrenceEditor({
                    start: options.model.start,
                    timezone: options.timezone,
                    messages: options.messages
                });
        },
        MOBILERECURRENCEEDITOR = function (container, options) {
            $('<div ' + kendo.attr("bind") + '="value:' + options.field + '" />')
                .attr({
                    name: options.field
                })
                .appendTo(container)
                .kendoMobileRecurrenceEditor({
                    start: options.model.start,
                    timezone: options.timezone,
                    messages: options.messages,
                    pane: options.pane,
                    value: options.model[options.field]
                });
        },
        MOBILETIMEZONEPOPUP = function (container, options) {
            var text = timezoneButtonText(options.model, options.messages.noTimezone);

            $('<a href="#" class="k-button k-timezone-button" data-bind="invisible:isAllDay">' + text + '</a>').click(options.click).appendTo(container);
        },
        TIMEZONEPOPUP = function (container, options) {
            $('<a href="#" class="k-button" data-bind="invisible:isAllDay">' + options.messages.timezoneEditorButton + '</a>').click(options.click).appendTo(container);
        },
        MOBILETIMEZONEEDITOR = function (container, options) {
            $('<div ' + kendo.attr("bind") + '="value:' + options.field + '" />')
                .attr({
                    name: options.field
                })
                .toggle(options.visible)
                .appendTo(container)
                .kendoMobileTimezoneEditor({
                    optionLabel: options.noTimezone
                });
        },
        TIMEZONEEDITOR = function (container, options) {
            $('<div ' + kendo.attr("bind") + '="value:' + options.field + '" />')
                .attr({ name: options.field })
                .toggle(options.visible)
                .appendTo(container)
                .kendoTimezoneEditor({
                    optionLabel: options.noTimezone
                });
        };

    function timezoneButtonText(model, message) {
        message = message || "";

        if (model.startTimezone) {
            message = model.startTimezone;

            if (model.endTimezone) {
                message += " | " + model.endTimezone;
            }
        }

        return message;
    }

    function appendTimezoneAttr(attrs, options) {
        var timezone = options.timezone;

        if (timezone) {
            attrs[kendo.attr("timezone")] = timezone;
        }
    }

    function appendDateCompareValidator(attrs, options) {
        var validationRules = options.model.fields[options.field].validation;

        if (validationRules) {
            var dateCompareRule = validationRules.dateCompare;
            if (dateCompareRule && isPlainObject(dateCompareRule) && dateCompareRule.message) {
                attrs[kendo.attr("dateCompare-msg")] = dateCompareRule.message;
            }
        }
    }

    function wrapDataAccess(originalFunction, timezone) {
        return function (data) {
            data = originalFunction(data);

            convertData(data, "apply", timezone);

            return data || [];
        };
    }

    function wrapDataSerialization(originalFunction, timezone) {
        return function (data) {

            if (data) {
                if (toString.call(data) !== "[object Array]" && !(data instanceof kendo.data.ObservableArray)) {
                    data = [data];
                }
            }

            convertData(data, "remove", timezone, true);

            data = originalFunction(data);

            return data || [];
        };
    }

    function convertData(data, method, timezone, removeUid) {
        var event,
            idx,
            length;

        data = data || [];

        for (idx = 0, length = data.length; idx < length; idx++) {
            event = data[idx];

            if (removeUid) {
                if (event.startTimezone || event.endTimezone) {
                    if (timezone) {
                        event.start = kendo.timezone.convert(event.start, event.startTimezone || event.endTimezone, timezone);
                        event.end = kendo.timezone.convert(event.end, event.endTimezone || event.startTimezone, timezone);

                        event.start = kendo.timezone[method](event.start, timezone);
                        event.end = kendo.timezone[method](event.end, timezone);
                    } else {
                        event.start = kendo.timezone[method](event.start, event.startTimezone || event.endTimezone);
                        event.end = kendo.timezone[method](event.end, event.endTimezone || event.startTimezone);
                    }

                } else if (timezone) {
                    event.start = kendo.timezone[method](event.start, timezone);
                    event.end = kendo.timezone[method](event.end, timezone);
                }
            } else {
                if (event.startTimezone || event.endTimezone) {
                    event.start = kendo.timezone[method](event.start, event.startTimezone || event.endTimezone);
                    event.end = kendo.timezone[method](event.end, event.endTimezone || event.startTimezone);

                    if (timezone) {
                        event.start = kendo.timezone.convert(event.start, event.startTimezone || event.endTimezone, timezone);
                        event.end = kendo.timezone.convert(event.end, event.endTimezone || event.startTimezone, timezone);
                    }

                } else if (timezone) {
                    event.start = kendo.timezone[method](event.start, timezone);
                    event.end = kendo.timezone[method](event.end, timezone);
                }
            }

            if (removeUid) {
                delete event.uid;
            }
        }
        return data;
    }

    function getOccurrenceByUid(data, uid) {
        var length = data.length,
            idx = 0,
            event;

        for (; idx < length; idx++) {
            event = data[idx];

            if (event.uid === uid) {
                return event;
            }
        }
    }

    var SchedulerDataReader = kendo.Class.extend({
        init: function (schema, reader) {
            var timezone = schema.timezone;

            this.reader = reader;

            if (reader.model) {
                this.model = reader.model;
            }

            this.timezone = timezone;
            this.data = wrapDataAccess($.proxy(this.data, this), timezone);
            this.serialize = wrapDataSerialization($.proxy(this.serialize, this), timezone);
        },
        errors: function (data) {
            return this.reader.errors(data);
        },
        parse: function (data) {
            return this.reader.parse(data);
        },
        data: function (data) {
            return this.reader.data(data);
        },
        total: function (data) {
            return this.reader.total(data);
        },
        groups: function (data) {
            return this.reader.groups(data);
        },
        aggregates: function (data) {
            return this.reader.aggregates(data);
        },
        serialize: function (data) {
            return this.reader.serialize(data);
        }
    });

    function applyZone(date, fromZone, toZone) {
        if (toZone) {
            date = kendo.timezone.convert(date, fromZone, toZone);
        } else {
            date = kendo.timezone.remove(date, fromZone);
        }

        return date;
    }

    function dateCompareValidator(input) {
        if (input.filter("[name=end]").length) {
            var container = input.closest(".k-scheduler-edit-form");
            var startInput = container.find("[name=start]:visible");
            var endInput = container.find("[name=end]:visible");

            if (endInput[0] && startInput[0]) {
                var start, end;
                var startPicker = kendo.widgetInstance(startInput, kendo.ui);
                var endPicker = kendo.widgetInstance(endInput, kendo.ui);

                var editable = container.data("kendoEditable");
                var model = editable ? editable.options.model : null;

                if (startPicker && endPicker) {
                    start = startPicker.value();
                    end = endPicker.value();
                } else {
                    start = kendo.parseDate(startInput.val());
                    end = kendo.parseDate(endInput.val());
                }

                if (start && end) {
                    if (model) {
                        var timezone = startInput.attr(kendo.attr("timezone"));
                        var startTimezone = model.startTimezone;
                        var endTimezone = model.endTimezone;

                        startTimezone = startTimezone || endTimezone;
                        endTimezone = endTimezone || startTimezone;

                        if (startTimezone) {
                            start = applyZone(start, startTimezone, timezone);
                            end = applyZone(end, endTimezone, timezone);
                        }
                    }

                    return start <= end;
                }
            }
        }

        return true;
    }

    var SchedulerEvent = kendo.data.Model.define({
        init: function (value) {
            var that = this;

            kendo.data.Model.fn.init.call(that, value);

            that._defaultId = that.defaults[that.idField];
        },

        _time: function (field) {
            var date = this[field];
            var fieldTime = field + "Time";

            if (this[fieldTime]) {
                return this[fieldTime] - kendo.date.toUtcTime(kendo.date.getDate(date));
            }

            return getMilliseconds(date);
        },

        _date: function (field) {
            var fieldTime = field + "Time";

            if (this[fieldTime]) {
                return this[fieldTime] - this._time(field);
            }

            return kendo.date.getDate(this[field]);
        },

        clone: function (options, updateUid) {
            var uid = this.uid,
                event = new this.constructor($.extend({}, this.toJSON(), options));

            if (!updateUid) {
                event.uid = uid;
            }

            return event;
        },

        duration: function () {
            var end = this.end;
            var start = this.start;
            var offset = (end.getTimezoneOffset() - start.getTimezoneOffset()) * kendo.date.MS_PER_MINUTE;

            return end - start - offset;
        },

        expand: function (start, end, zone) {
            return recurrence ? recurrence.expand(this, start, end, zone) : [this];
        },

        update: function (eventInfo) {
            for (var field in eventInfo) {
                this.set(field, eventInfo[field]);
            }

            if (this.startTime) {
                this.set("startTime", kendo.date.toUtcTime(this.start));
            }

            if (this.endTime) {
                this.set("endTime", kendo.date.toUtcTime(this.end));
            }
        },

        isMultiDay: function () {
            return this.isAllDay || this.duration() >= kendo.date.MS_PER_DAY;
        },

        isException: function () {
            return !this.isNew() && this.recurrenceId;
        },

        isOccurrence: function () {
            return this.isNew() && this.recurrenceId;
        },

        isRecurring: function () {
            return !!(this.recurrenceRule || this.recurrenceId);
        },

        isRecurrenceHead: function () {
            return !!(this.id && this.recurrenceRule);
        },

        toOccurrence: function (options) {
            options = $.extend(options, {
                recurrenceException: null,
                recurrenceRule: null,
                recurrenceId: this.id || this.recurrenceId
            });

            options[this.idField] = this.defaults[this.idField];

            return this.clone(options, true);
        },

        toJSON: function () {
            var obj = kendo.data.Model.fn.toJSON.call(this);
            obj.uid = this.uid;

            delete obj.startTime;
            delete obj.endTime;

            return obj;
        },

        shouldSerialize: function (field) {
            return kendo.data.Model.fn.shouldSerialize.call(this, field) && field !== "_defaultId";
        },

        set: function (key, value) {
            var isAllDay = this.isAllDay || false;

            kendo.data.Model.fn.set.call(this, key, value);

            if (key == "isAllDay" && value != isAllDay) {
                var start = kendo.date.getDate(this.start);
                var end = new Date(this.end);
                var milliseconds = kendo.date.getMilliseconds(end);

                if (milliseconds === 0 && value) {
                    milliseconds = MS_PER_DAY;
                }

                this.set("start", start);

                if (value === true) {
                    kendo.date.setTime(end, -milliseconds);

                    if (end < start) {
                        end = start;
                    }
                } else {
                    kendo.date.setTime(end, MS_PER_DAY - milliseconds);
                }

                this.set("end", end);
            }
        },
        id: "id",
        fields: {
            id: { type: "number" },
            title: { defaultValue: "", type: "string" },
            start: { type: "date", validation: { required: true} },
            startTimezone: { type: "string" },
            end: { type: "date", validation: { required: true, dateCompare: { value: dateCompareValidator, message: "End date should be greater than or equal to the start date"}} },
            endTimezone: { type: "string" },
            recurrenceRule: { defaultValue: "", type: "string" },
            recurrenceException: { defaultValue: "", type: "string" },
            isAllDay: { type: "boolean", defaultValue: false },
            description: { type: "string" }
        }
    });

    var SchedulerDataSource = DataSource.extend({
        init: function (options) {

            DataSource.fn.init.call(this, extend(true, {}, {
                schema: {
                    modelBase: SchedulerEvent,
                    model: SchedulerEvent
                }
            }, options));

            this.reader = new SchedulerDataReader(this.options.schema, this.reader);
        },

        expand: function (start, end) {
            var data = this.view(),
                filter = {};

            if (start && end) {
                end = new Date(end.getTime() + MS_PER_DAY - 1);

                filter = {
                    logic: "or",
                    filters: [
                        {
                            logic: "and",
                            filters: [
                                { field: "start", operator: "gte", value: start },
                                { field: "end", operator: "gte", value: start },
                                { field: "start", operator: "lte", value: end }
                            ]
                        },
                        {
                            logic: "and",
                            filters: [
                                { field: "start", operator: "lte", value: new Date(start.getTime() + MS_PER_DAY - 1) },
                                { field: "end", operator: "gte", value: start }
                            ]
                        }
                    ]
                };

                data = new kendo.data.Query(expandAll(data, start, end, this.reader.timezone)).filter(filter).toArray();
            }

            return data;
        },

        cancelChanges: function (model) {
            if (model && model.isOccurrence()) {
                this._removeExceptionDate(model);
            }

            DataSource.fn.cancelChanges.call(this, model);
        },

        insert: function (index, model) {
            if (!model) {
                return;
            }

            if (!(model instanceof SchedulerEvent)) {
                var eventInfo = model;

                model = this._createNewModel();
                model.accept(eventInfo);
            }

            if (model.isRecurrenceHead() || model.recurrenceId) {
                model = model.recurrenceId ? model : model.toOccurrence();
                this._addExceptionDate(model);
            }

            return DataSource.fn.insert.call(this, index, model);
        },

        remove: function (model) {
            if (model.isRecurrenceHead()) {
                this._removeExceptions(model);
            } else if (model.isRecurring()) {
                this._addExceptionDate(model);
            }

            return DataSource.fn.remove.call(this, model);
        },

        _removeExceptions: function (model) {
            var data = this.data().slice(0),
                item = data.shift(),
                id = model.id;

            while (item) {
                if (item.recurrenceId === id) {
                    DataSource.fn.remove.call(this, item);
                }

                item = data.shift();
            }

            model.set(RECURRENCE_EXCEPTION, "");
        },

        _removeExceptionDate: function (model) {
            if (model.recurrenceId) {
                var head = this.get(model.recurrenceId);

                if (head) {
                    var start = model.start;

                    head.set(RECURRENCE_EXCEPTION, head.recurrenceException.replace(recurrence.toExceptionString(start, this.reader.timezone), ""));
                }
            }
        },

        _addExceptionDate: function (model) {
            var start = model.start;
            var zone = this.reader.timezone;
            var head = this.get(model.recurrenceId);
            var recurrenceException = head.recurrenceException || "";

            if (!recurrence.isException(recurrenceException, start, zone)) {
                head.set(RECURRENCE_EXCEPTION, recurrenceException + recurrence.toExceptionString(start, zone));
            }
        }
    });

    function expandAll(events, start, end, zone) {
        var length = events.length,
            data = [],
            idx = 0;

        for (; idx < length; idx++) {
            data = data.concat(events[idx].expand(start, end, zone));
        }

        return data;
    }

    SchedulerDataSource.create = function (options) {
        options = options && options.push ? { data: options} : options;

        var dataSource = options || {},
            data = dataSource.data;

        dataSource.data = data;

        if (!(dataSource instanceof SchedulerDataSource) && dataSource instanceof kendo.data.DataSource) {
            throw new Error("Incorrect DataSource type. Only SchedulerDataSource instances are supported");
        }

        return dataSource instanceof SchedulerDataSource ? dataSource : new SchedulerDataSource(dataSource);
    };

    extend(true, kendo.data, {
        SchedulerDataSource: SchedulerDataSource,
        SchedulerDataReader: SchedulerDataReader,
        SchedulerEvent: SchedulerEvent
    });

    var defaultCommands = {
        update: {
            text: "Save",
            className: "k-primary k-scheduler-update"
        },
        canceledit: {
            text: "Cancel",
            className: "k-scheduler-cancel"
        },
        destroy: {
            text: "Delete",
            imageClass: "k-delete",
            className: "k-primary k-scheduler-delete",
            iconClass: "k-icon"
        }
    };

    function trimOptions(options) {
        delete options.name;
        delete options.prefix;

        delete options.remove;
        delete options.edit;
        delete options.add;
        delete options.navigate;

        return options;
    }

    function dropDownResourceEditor(resource) {
        return function (container) {
            $(kendo.format('<select data-{0}bind="value:{1}">', kendo.ns, resource.field))
             .appendTo(container)
             .kendoDropDownList({
                 dataTextField: resource.dataTextField,
                 dataValueField: resource.dataValueField,
                 dataSource: resource.dataSource,
                 valuePrimitive: resource.valuePrimitive,
                 optionLabel: "None",
                 template: kendo.format('<span class="k-scheduler-mark" style="background-color:#= data.{0}?{0}:"none" #"></span>#={1}#', resource.dataColorField, resource.dataTextField)
             });
        };
    }

    function dropDownResourceEditorMobile(resource) {
        return function (container) {
            var options = '';
            var view = resource.dataSource.view();

            for (var idx = 0, length = view.length; idx < length; idx++) {
                options += kendo.format('<option value="{0}">{1}</option>',
                    kendo.getter(resource.dataValueField)(view[idx]),
                    kendo.getter(resource.dataTextField)(view[idx])
                );
            }

            $(kendo.format('<select data-{0}bind="value:{1}">{2}</select>', kendo.ns, resource.field, options))
             .appendTo(container);
        };
    }

    function multiSelectResourceEditor(resource) {
        return function (container) {
            $(kendo.format('<select data-{0}bind="value:{1}">', kendo.ns, resource.field))
             .appendTo(container)
             .kendoMultiSelect({
                 dataTextField: resource.dataTextField,
                 dataValueField: resource.dataValueField,
                 dataSource: resource.dataSource,
                 valuePrimitive: resource.valuePrimitive,
                 itemTemplate: kendo.format('<span class="k-scheduler-mark" style="background-color:#= data.{0}?{0}:"none" #"></span>#={1}#', resource.dataColorField, resource.dataTextField),
                 tagTemplate: kendo.format('<span class="k-scheduler-mark" style="background-color:#= data.{0}?{0}:"none" #"></span>#={1}#', resource.dataColorField, resource.dataTextField)
             });
        };
    }

    function multiSelectResourceEditorMobile(resource) {
        return function (container) {
            var options = "";
            var view = resource.dataSource.view();

            for (var idx = 0, length = view.length; idx < length; idx++) {
                options += kendo.format('<option value="{0}">{1}</option>',
                    kendo.getter(resource.dataValueField)(view[idx]),
                    kendo.getter(resource.dataTextField)(view[idx])
                );
            }

            $(kendo.format('<select data-{0}bind="value:{1}" multiple="multiple" data-{0}value-primitive="{3}">{2}</select>',
                kendo.ns,
                resource.field,
                options,
                resource.valuePrimitive
             ))
             .appendTo(container);
        };
    }

    function moveEventRange(event, distance) {
        var duration = event.end.getTime() - event.start.getTime();

        var start = new Date(event.start.getTime());

        kendo.date.setTime(start, distance);

        var end = new Date(start.getTime());

        kendo.date.setTime(end, duration, true);

        return {
            start: start,
            end: end
        };
    }

    var editors = {
        mobile: {
            dateRange: MOBILEDATERANGEEDITOR,
            timezonePopUp: MOBILETIMEZONEPOPUP,
            timezone: MOBILETIMEZONEEDITOR,
            recurrence: MOBILERECURRENCEEDITOR,
            description: '<textarea name="description" class="k-textbox"/>',
            multipleResources: multiSelectResourceEditorMobile,
            resources: dropDownResourceEditor
        },
        desktop: {
            dateRange: DATERANGEEDITOR,
            timezonePopUp: TIMEZONEPOPUP,
            timezone: TIMEZONEEDITOR,
            recurrence: RECURRENCEEDITOR,
            description: '<textarea name="description" class="k-textbox"/>',
            multipleResources: multiSelectResourceEditor,
            resources: dropDownResourceEditor
        }
    };

    var Editor = kendo.Observable.extend({
        init: function (element, options) {

            kendo.Observable.fn.init.call(this);

            this.element = element;
            this.options = extend(true, {}, this.options, options);
            this.createButton = this.options.createButton;

            this.toggleDateValidationHandler = proxy(this._toggleDateValidation, this);
        },

        _toggleDateValidation: function (e) {
            if (e.field == "isAllDay") {
                var container = this.container,
                    isAllDay = this.editable.options.model.isAllDay,
                    bindAttribute = kendo.attr("bind"),
                    element, isDateTimeInput, shouldValidate;
                container.find("[" + bindAttribute + "*=end],[" + bindAttribute + "*=start]").each(function () {
                    element = $(this);
                    if (valueStartEndBoundRegex.test(element.attr(bindAttribute))) {
                        isDateTimeInput = element.is("[" + kendo.attr("role") + "=datetimepicker],[type*=datetime]");
                        shouldValidate = isAllDay !== isDateTimeInput;
                        element.attr(kendo.attr("validate"), shouldValidate);
                    }
                });
            }
        },

        fields: function (editors, model) {
            var that = this;

            var messages = that.options.messages;
            var timezone = that.options.timezone;

            var click = function (e) {
                e.preventDefault();
                that._initTimezoneEditor(model, this);
            };

            var fields = [
                { field: "title", title: messages.editor.title /*, format: field.format, editor: field.editor, values: field.values*/ },
                { field: "start", title: messages.editor.start, editor: editors.dateRange, timezone: timezone },
                { field: "end", title: messages.editor.end, editor: editors.dateRange, timezone: timezone },
                { field: "isAllDay", title: messages.editor.allDayEvent }
            ];

            if (kendo.timezone.windows_zones) {
                fields.push({ field: "timezone", title: messages.editor.timezone, editor: editors.timezonePopUp, click: click, messages: messages.editor, model: model });
                fields.push({ field: "startTimezone", title: messages.editor.startTimezone, editor: editors.timezone, noTimezone: messages.editor.noTimezone });
                fields.push({ field: "endTimezone", title: messages.editor.endTimezone, editor: editors.timezone, noTimezone: messages.editor.noTimezone });
            }

            if (!model.recurrenceId) {
                fields.push({ field: "recurrenceRule", title: messages.editor.repeat, editor: editors.recurrence, timezone: timezone, messages: messages.recurrenceEditor, pane: this.pane });
            }

            if ("description" in model) {
                fields.push({ field: "description", title: messages.editor.description, editor: editors.description });
            }

            for (var resourceIndex = 0; resourceIndex < this.options.resources.length; resourceIndex++) {
                var resource = this.options.resources[resourceIndex];
                fields.push({
                    field: resource.field,
                    title: resource.title,
                    editor: resource.multiple ? editors.multipleResources(resource) : editors.resources(resource)
                });
            }

            return fields;
        },

        end: function () {
            return this.editable.end();
        },

        _buildEditTemplate: function (model, fields, editableFields) {
            var messages = this.options.messages;
            var settings = extend({}, kendo.Template, this.options.templateSettings);
            var paramName = settings.paramName;
            var template = this.options.editable.template;

            var html = "";

            if (template) {
                if (typeof template === STRING) {
                    template = window.unescape(template);
                }
                html += (kendo.template(template, settings))(model);
            } else {
                for (var idx = 0, length = fields.length; idx < length; idx++) {
                    var field = fields[idx];

                    if (field.field === "startTimezone") {
                        html += '<div class="k-popup-edit-form k-scheduler-edit-form k-scheduler-timezones" style="display:none">';
                        html += '<div class="k-edit-form-container">';
                        html += '<div class="k-edit-label"></div>';
                        html += '<div class="k-edit-field"><label class="k-check"><input class="k-timezone-toggle" type="checkbox" />' + messages.editor.separateTimezones + '</label></div>';
                    }

                    html += '<div class="k-edit-label"><label for="' + field.field + '">' + (field.title || field.field || "") + '</label></div>';

                    if ((!model.editable || model.editable(field.field))) {
                        editableFields.push(field);
                        html += '<div ' + kendo.attr("container-for") + '="' + field.field + '" class="k-edit-field"></div>';
                    } else {
                        var tmpl = "#:";

                        if (field.field) {
                            field = kendo.expr(field.field, paramName);
                            tmpl += field + "==null?'':" + field;
                        } else {
                            tmpl += "''";
                        }

                        tmpl += "#";

                        tmpl = kendo.template(tmpl, settings);

                        html += '<div class="k-edit-field">' + tmpl(model) + '</div>';
                    }

                    if (field.field === "endTimezone") {
                        html += this._createEndTimezoneButton();
                    }
                }
            }

            return html;
        },

        _createEndTimezoneButton: function () {
            return '</div></div>';
        },

        _revertTimezones: function (model) {
            model.set("startTimezone", this._startTimezone);
            model.set("endTimezone", this._endTimezone);

            delete this._startTimezone;
            delete this._endTimezone;
        }
    });

    var MobileEditor = Editor.extend({
        init: function () {
            Editor.fn.init.apply(this, arguments);

            this.pane = kendo.mobile.ui.Pane.wrap(this.element);
            this.pane.element.parent().css("height", this.options.height);
            this.view = this.pane.view();
            this._actionSheetButtonTemplate = kendo.template('<li><a #=attr# class="k-button #=className#" href="\\#">#:text#</a></li>');

            this._actionSheetPopupOptions = $(document.documentElement).hasClass("km-root") ? { modal: false} : {
                align: "bottom center",
                position: "bottom center",
                effect: "slideIn:up"
            };
        },

        options: {
            animations: {
                left: "slide",
                right: "slide:right"
            }
        },

        destroy: function () {
            this.close();
            this.unbind();
            this.pane.destroy();
        },

        _initTimezoneEditor: function (model) {
            var that = this;
            var pane = that.pane;
            var messages = that.options.messages;
            var timezoneView = that.timezoneView;
            var container = that.container.find(".k-scheduler-timezones");
            var checkbox = container.find(".k-timezone-toggle");
            var endTimezoneRow = container.find(".k-edit-label:last").add(container.find(".k-edit-field:last"));
            var startTimezoneChange = function (e) {
                if (e.field === "startTimezone") {
                    var value = model.startTimezone;

                    checkbox.prop("disabled", !value);

                    if (!value) {
                        endTimezoneRow.hide();
                        model.set("endTimezone", "");
                        checkbox.prop("checked", false);
                    }
                }
            };

            that._startTimezone = model.startTimezone || "";
            that._endTimezone = model.endTimezone || "";

            if (!timezoneView) {
                var html = '<div data-role="view" class="k-popup-edit-form k-scheduler-edit-form k-mobile-list">' +
                           '<div data-role="header" class="k-header"><a href="#" class="k-button k-scheduler-cancel">' + messages.cancel + '</a>' +
                           messages.editor.timezoneTitle + '<a href="#" class="k-button k-scheduler-update">' + messages.save + '</a></div></div>';

                this.timezoneView = timezoneView = pane.append(html);

                timezoneView.contentElement().append(container.show());

                timezoneView.element.on(CLICK + NS, ".k-scheduler-cancel, .k-scheduler-update", function (e) {
                    e.preventDefault();
                    e.stopPropagation();

                    if ($(this).hasClass("k-scheduler-cancel")) {
                        that._revertTimezones(model);
                    }

                    model.unbind("change", startTimezoneChange);

                    var editView = pane.element.find("#edit").data("kendoMobileView");

                    var text = timezoneButtonText(model, messages.editor.noTimezone);

                    editView.contentElement().find(".k-timezone-button").text(text);

                    pane.navigate(editView, that.options.animations.right);
                });

                checkbox.click(function () {
                    endTimezoneRow.toggle(checkbox.prop("checked"));
                    model.set("endTimezone", "");
                });

                model.bind("change", startTimezoneChange);
            }

            checkbox.prop("checked", model.endTimezone).prop("disabled", !model.startTimezone);

            if (model.endTimezone) {
                endTimezoneRow.show();
            } else {
                endTimezoneRow.hide();
            }

            pane.navigate(timezoneView, that.options.animations.left);
        },

        _createActionSheetButton: function (options) {
            options.template = this._actionSheetButtonTemplate;
            return this.createButton(options);
        },

        showDialog: function (options) {
            var type = "";
            var html = "<ul><li class=\"km-actionsheet-title\">" + options.title + "</li>";

            var target = this.element.find(".k-event[" + kendo.attr("uid") + "='" + options.model.uid + "']");

            if (this.container) {
                target = this.container.find(".k-scheduler-delete");

                if (target[0]) {
                    type = 'phone';
                }
            }

            for (var buttonIndex = 0; buttonIndex < options.buttons.length; buttonIndex++) {
                html += this._createActionSheetButton(options.buttons[buttonIndex]);
            }

            html += "</ul>";

            var actionSheet = $(html)
                .appendTo(this.pane.view().element)
                .kendoMobileActionSheet({
                    type: type,
                    cancel: this.options.messages.cancel,
                    cancelTemplate: '<li class="km-actionsheet-cancel"><a class="k-button" href="\\#">#:cancel#</a></li>',
                    close: function () {
                        this.destroy();
                    },
                    command: function (e) {
                        var buttonIndex = actionSheet.element.find("li:not(.km-actionsheet-cancel) > .k-button").index($(e.currentTarget));
                        if (buttonIndex > -1) {
                            actionSheet.close();
                            options.buttons[buttonIndex].click();
                        }
                    },
                    popup: this._actionSheetPopupOptions
                })
                .data("kendoMobileActionSheet");

            actionSheet.open(target);
        },

        editEvent: function (model) {
            var pane = this.pane;
            var html = "";

            var messages = this.options.messages;
            var updateText = messages.save;
            var removeText = messages.destroy;
            var cancelText = messages.cancel;
            var titleText = messages.editor.editorTitle;

            html += '<div data-role="view" class="k-popup-edit-form k-scheduler-edit-form k-mobile-list" id="edit" ' + kendo.attr("uid") + '="' + model.uid + '">' +
                '<div data-role="header" class="k-header"><a href="#" class="k-button k-scheduler-cancel">' + cancelText + '</a>' +
                titleText + '<a href="#" class="k-button k-scheduler-update">' + updateText + '</a></div>';

            var fields = this.fields(editors.mobile, model);

            var that = this;

            var editableFields = [];

            html += this._buildEditTemplate(model, fields, editableFields);

            if (!model.isNew() && this.options.editable && this.options.editable.destroy !== false) {
                html += '<div class="k-edit-buttons"><a href="#" class="k-scheduler-delete k-button">' + removeText + '</a></div>';
            }

            html += "</div>";

            var view = pane.append(html);

            var container = this.container = view.element;

            this.editable = container.kendoEditable({
                fields: editableFields,
                model: model,
                clearContainer: false,
                target: that.options.target,

                validateOnBlur: true
            }).data("kendoEditable");

            // TODO: Replace this code with labels and for="ID"
            container.find("input[type=checkbox],input[type=radio]")
                     .parent(".k-edit-field")
                     .addClass("k-check")
                     .prev(".k-edit-label")
                     .addClass("k-check")
                     .click(function () {
                         $(this).next().children("input").click();
                     });

            if (!this.trigger("edit", { container: container, model: model })) {

                container.on(CLICK + NS, "a.k-scheduler-edit, a.k-scheduler-cancel, a.k-scheduler-update, a.k-scheduler-delete", function (e) {
                    e.preventDefault();
                    e.stopPropagation();

                    var button = $(this);

                    if (!button.hasClass("k-scheduler-edit")) {

                        var name = "cancel";

                        if (button.hasClass("k-scheduler-update")) {
                            name = "save";
                        } else if (button.hasClass("k-scheduler-delete")) {
                            name = "remove";
                        }

                        that.trigger(name, { container: container, model: model });
                    } else {
                        pane.navigate("#edit", that.options.animations.right);
                    }
                });

                pane.navigate(view, that.options.animations.left);

                model.bind("change", that.toggleDateValidationHandler);
            } else {
                this.trigger("cancel", { container: container, model: model });
            }

            return this.editable;
        },

        _views: function () {
            return this.pane.element
                    .find(kendo.roleSelector("view"))
                    .not(this.view.element);
        },

        close: function () {
            if (this.container) {
                this.pane.navigate("", this.options.animations.right);

                var views = this._views();
                var view;

                for (var idx = 0, length = views.length; idx < length; idx++) {
                    view = views.eq(idx).data("kendoMobileView");
                    if (view) {
                        view.purge();
                    }
                }

                views.remove();

                this.container = null;
                if (this.editable) {
                    this.editable.options.model.unbind("change", this.toggleDateValidationHandler);
                    this.editable.destroy();
                    this.editable = null;
                }
                this.timezoneView = null;
            }
        }
    });

    var PopupEditor = Editor.extend({
        destroy: function () {
            this.close();
            this.unbind();
        },

        editEvent: function (model) {
            var that = this;
            var editable = that.options.editable;
            var html = '<div ' + kendo.attr("uid") + '="' + model.uid + '" class="k-popup-edit-form k-scheduler-edit-form"><div class="k-edit-form-container">';
            var messages = that.options.messages;
            var updateText = messages.save;
            var cancelText = messages.cancel;
            var deleteText = messages.destroy;

            var fields = this.fields(editors.desktop, model);

            var editableFields = [];

            html += this._buildEditTemplate(model, fields, editableFields);

            var attr;
            var options = isPlainObject(editable) ? editable.window : {};

            html += '<div class="k-edit-buttons k-state-default">';
            html += this.createButton({ name: "update", text: updateText, attr: attr }) + this.createButton({ name: "canceledit", text: cancelText, attr: attr });

            if (!model.isNew() && editable.destroy !== false) {
                html += this.createButton({ name: "delete", text: deleteText, attr: attr });
            }

            html += '</div></div></div>';

            var container = this.container = $(html)
                .appendTo(that.element).eq(0)
                .kendoWindow(extend({
                    modal: true,
                    resizable: false,
                    draggable: true,
                    title: messages.editor.editorTitle,
                    visible: false,
                    close: function (e) {
                        if (e.userTriggered) {
                            if (that.trigger(CANCEL, { container: container, model: model })) {
                                e.preventDefault();
                            }
                        }
                    }
                }, options));

            that.editable = container.kendoEditable({
                fields: editableFields,
                model: model,
                clearContainer: false,
                validateOnBlur: true,
                target: that.options.target
            }).data("kendoEditable");

            if (!that.trigger(EDIT, { container: container, model: model })) {

                container.data("kendoWindow").center().open();

                container.on(CLICK + NS, "a.k-scheduler-cancel", function (e) {
                    e.preventDefault();
                    e.stopPropagation();

                    that.trigger(CANCEL, { container: container, model: model });
                });

                container.on(CLICK + NS, "a.k-scheduler-update", function (e) {
                    e.preventDefault();
                    e.stopPropagation();

                    that.trigger("save", { container: container, model: model });
                });

                container.on(CLICK + NS, "a.k-scheduler-delete", function (e) {
                    e.preventDefault();
                    e.stopPropagation();

                    that.trigger(REMOVE, { container: container, model: model });
                });

                model.bind("change", that.toggleDateValidationHandler);
            } else {
                that.trigger(CANCEL, { container: container, model: model });
            }

            return that.editable;
        },

        close: function () {
            var that = this;

            var destroy = function () {
                if (that.editable) {
                    that.editable.options.model.unbind("change", that.toggleDateValidationHandler);
                    that.editable.destroy();
                    that.editable = null;
                    that.container = null;
                }
                if (that.popup) {
                    that.popup.destroy();
                    that.popup = null;
                }
            };

            if (that.editable) {
                if (that._timezonePopup && that._timezonePopup.data("kendoWindow")) {
                    that._timezonePopup.data("kendoWindow").destroy();
                    that._timezonePopup = null;
                }

                if (that.container.is(":visible")) {
                    that.container.data("kendoWindow").bind("deactivate", destroy).close();
                } else {
                    destroy();
                }
            } else {
                destroy();
            }
        },

        _createEndTimezoneButton: function () {
            var messages = this.options.messages;
            var html = "";

            html += '<div class="k-edit-buttons k-state-default">';
            html += this.createButton({ name: "savetimezone", text: messages.save }) + this.createButton({ name: "canceltimezone", text: messages.cancel });
            html += '</div></div></div>';

            return html;
        },

        showDialog: function (options) {
            var html = kendo.format("<div class='k-popup-edit-form'><div class='k-edit-form-container'><p class='k-popup-message'>{0}</p>", options.text);

            html += '<div class="k-edit-buttons k-state-default">';

            for (var buttonIndex = 0; buttonIndex < options.buttons.length; buttonIndex++) {
                html += this.createButton(options.buttons[buttonIndex]);
            }

            html += '</div></div></div>';

            var wrapper = this.element;

            if (this.popup) {
                this.popup.destroy();
            }

            var popup = this.popup = $(html).appendTo(wrapper)
                               .eq(0)
                               .on("click", ".k-button", function (e) {
                                   e.preventDefault();

                                   popup.close();

                                   var buttonIndex = $(e.currentTarget).index();

                                   options.buttons[buttonIndex].click();
                               })
                               .kendoWindow({
                                   modal: true,
                                   resizable: false,
                                   draggable: false,
                                   title: options.title,
                                   visible: false,
                                   close: function () {
                                       this.destroy();
                                       wrapper.focus();
                                   }
                               })
                               .getKendoWindow();

            popup.center().open();
        },

        _initTimezoneEditor: function (model, activator) {
            var that = this;
            var container = that.container.find(".k-scheduler-timezones");
            var checkbox = container.find(".k-timezone-toggle");
            var endTimezoneRow = container.find(".k-edit-label:last").add(container.find(".k-edit-field:last"));
            var saveButton = container.find(".k-scheduler-savetimezone");
            var cancelButton = container.find(".k-scheduler-canceltimezone");
            var timezonePopup = that._timezonePopup;
            var startTimezoneChange = function (e) {
                if (e.field === "startTimezone") {
                    var value = model.startTimezone;

                    checkbox.prop("disabled", !value);

                    if (!value) {
                        endTimezoneRow.hide();
                        model.set("endTimezone", "");
                        checkbox.prop("checked", false);
                    }
                }
            };
            var wnd;

            that._startTimezone = model.startTimezone;
            that._endTimezone = model.endTimezone;

            if (!timezonePopup) {
                that._timezonePopup = timezonePopup = container.kendoWindow({
                    modal: true,
                    resizable: false,
                    draggable: true,
                    title: that.options.messages.editor.timezoneEditorTitle,
                    visible: false,
                    close: function (e) {
                        model.unbind("change", startTimezoneChange);

                        if (e.userTriggered) {
                            that._revertTimezones(model);
                        }

                        if (activator) {
                            activator.focus();
                        }
                    }
                });

                checkbox.click(function () {
                    endTimezoneRow.toggle(checkbox.prop("checked"));
                    model.set("endTimezone", "");
                });

                saveButton.click(function (e) {
                    e.preventDefault();
                    wnd.close();
                });

                cancelButton.click(function (e) {
                    e.preventDefault();
                    that._revertTimezones(model);
                    wnd.close();
                });

                model.bind("change", startTimezoneChange);
            }

            checkbox.prop("checked", model.endTimezone).prop("disabled", !model.startTimezone);

            if (model.endTimezone) {
                endTimezoneRow.show();
            } else {
                endTimezoneRow.hide();
            }

            wnd = timezonePopup.data("kendoWindow");
            wnd.center().open();
        }
    });

    var Scheduler = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            if (!that.options.views || !that.options.views.length) {
                that.options.views = ["day", "week"];
            }

            that.resources = [];

            that._initModel();

            that._wrapper();

            that._views();

            that._toolbar();

            that._dataSource();

            that._resources();

            that._resizeHandler = proxy(that.resize, that);

            that.wrapper.on("mousedown" + NS + " selectstart" + NS, function (e) {
                e.preventDefault();
            });

            if (that.options.editable && that.options.editable.resize !== false) {
                that._resizable();
            }

            that._movable();

            $(window).on("resize" + NS, that._resizeHandler);

            if (that.options.messages && that.options.messages.recurrence) {
                recurrence.options = that.options.messages.recurrence;
            }

            that._selectable();

            that._ariaId = kendo.guid();

            that._createEditor();
        },

        _isMobile: function () {
            var options = this.options;
            return (options.mobile === true && kendo.support.mobileOS) || options.mobile === "phone" || options.mobile === "tablet";
        },

        _isMobilePhoneView: function () {
            var options = this.options;
            return (options.mobile === true && kendo.support.mobileOS && !kendo.support.mobileOS.tablet) || options.mobile === "phone";
        },

        _selectable: function () {
            var that = this,
                wrapper = that.wrapper,
                selectEvent = kendo.support.mobileOS ? "touchend" : "mousedown";

            if (!that.options.selectable) {
                return;
            }

            that._tabindex();

            wrapper.on(selectEvent + NS, ".k-scheduler-header-all-day td, .k-scheduler-content td, .k-event", function (e) {
                var which = e.which;
                var button = e.button;
                var browser = kendo.support.browser;
                var isRight = which && which === 3 || button && button == 2;

                if (kendo.support.mobileOS && e.isDefaultPrevented()) {
                    return;
                }

                if (!isRight) {
                    that._createSelection(e.currentTarget);
                }

                wrapper.focus();

                if (browser.msie && browser.version < 9) {
                    setTimeout(function () {
                        wrapper.focus();
                    });
                }
            });

            var mouseMoveHandler = $.proxy(that._mouseMove, that);

            wrapper.on("mousedown" + NS, ".k-scheduler-header-all-day td, .k-scheduler-content td", function (e) {
                var which = e.which;
                var button = e.button;
                var isRight = which && which === 3 || button && button == 2;

                if (!isRight) {
                    wrapper.on("mousemove" + NS, ".k-scheduler-header-all-day td, .k-scheduler-content td", mouseMoveHandler);
                }
            });

            wrapper.on("mouseup" + NS + " mouseleave" + NS, function () {
                wrapper.off("mousemove" + NS, ".k-scheduler-header-all-day td, .k-scheduler-content td", mouseMoveHandler);
            });

            wrapper.on("focus" + NS, function () {
                if (!that._selection) {
                    that._createSelection($(".k-scheduler-content").find("td:first"));
                }

                that._select();
            });

            wrapper.on("focusout" + NS, function () {
                that.view().clearSelection();
                that._ctrlKey = that._shiftKey = false;
            });

            wrapper.on("keydown" + NS, proxy(that._keydown, that));

            wrapper.on("keyup" + NS, function (e) {
                that._ctrlKey = e.ctrlKey;
                that._shiftKey = e.shiftKey;
            });
        },

        _select: function () {
            var that = this;
            var view = that.view();
            var wrapper = that.wrapper;
            var current = view.current();
            var selection = that._selection;

            if (current) {
                current.removeAttribute("id");
                current.removeAttribute("aria-label");
                wrapper.removeAttr("aria-activedescendant");
            }

            view.select(selection);

            current = view.current();

            if (current && that._old !== current) {
                var labelFormat;
                var data = selection;
                var events = that._selectedEvents();
                var slots = view._selectedSlots;

                if (events[0]) {
                    data = events[0] || selection;
                    labelFormat = kendo.format(that.options.messages.ariaEventLabel, data.title, data.start, data.start);
                } else {
                    labelFormat = kendo.format(that.options.messages.ariaSlotLabel, data.start, data.end);
                }

                current.setAttribute("id", that._ariaId);
                current.setAttribute("aria-label", labelFormat);
                wrapper.attr("aria-activedescendant", that._ariaId);

                that._old = current;

                that.trigger("change", {
                    start: selection.start,
                    end: selection.end,
                    events: events,
                    slots: slots,
                    resources: view._resourceBySlot(selection)
                });
            }
        },

        _selectedEvents: function () {
            var uids = this._selection.events;
            var length = uids.length;
            var idx = 0;
            var event;

            var events = [];

            for (; idx < length; idx++) {
                event = this.occurrenceByUid(uids[idx]);
                if (event) {
                    events.push(event);
                }
            }

            return events;
        },

        _mouseMove: function (e) {
            var that = this;
            clearTimeout(that._moveTimer);

            that._moveTimer = setTimeout(function () {
                var view = that.view();
                var selection = that._selection;

                if (selection) {
                    var slot = view.selectionByElement($(e.currentTarget));

                    if (slot && selection.groupIndex === slot.groupIndex) {
                        var startDate = slot.startDate();
                        var endDate = slot.endDate();

                        if (startDate >= selection.end) {
                            selection.backward = false;
                        } else if (endDate <= selection.start) {
                            selection.backward = true;
                        }

                        if (selection.backward) {
                            selection.start = startDate;
                        } else {
                            selection.end = endDate;
                        }

                        that._select();
                    }
                }
            }, 5);
        },

        _viewByIndex: function (index) {
            var view, views = this.views;

            for (view in views) {
                if (!index) {
                    return view;
                }

                index--;
            }
        },

        _keydown: function (e) {
            var that = this,
                key = e.keyCode,
                view = that.view(),
                editable = view.options.editable,
                selection = that._selection,
                shiftKey = e.shiftKey;

            that._ctrlKey = e.ctrlKey;
            that._shiftKey = e.shiftKey;

            if (key === keys.TAB) {
                if (view.moveToEvent(selection, shiftKey)) {
                    that._select();

                    e.preventDefault();
                }
            } else if (editable && key === keys.ENTER) {
                // add/edit event
                if (selection.events.length) {
                    if (editable.update !== false) {
                        that.editEvent(selection.events[0]);
                    }
                } else if (editable.create !== false) {
                    if (selection.isAllDay) {
                        selection = $.extend({}, selection, {
                            end: kendo.date.addDays(selection.end, -1)
                        });
                    }

                    that.addEvent(extend({}, selection, view._resourceBySlot(selection)));
                }
            } else if (key === keys.DELETE && editable !== false && editable.destroy !== false) {
                that.removeEvent(selection.events[0]);
            } else if (key >= 49 && key <= 57) {
                // switch to view 1-9
                that.view(that._viewByIndex(key - 49));
            } else if (view.move(selection, key, shiftKey)) {
                if (view.inRange(selection)) {
                    that._select();
                } else {
                    that.date(selection.start);
                }

                e.preventDefault();
            }

            that._adjustSelectedDate();
        },

        _createSelection: function (item) {
            var uid, slot, selection;

            if (!this._selection || (!this._ctrlKey && !this._shiftKey)) {
                this._selection = {
                    events: [],
                    groupIndex: 0
                };
            }

            item = $(item);
            selection = this._selection;
            uid = item.attr(kendo.attr("uid"));

            slot = this.view().selectionByElement(item);

            if (slot) {
                selection.groupIndex = slot.groupIndex || 0;
            }

            if (uid) {
                slot = getOccurrenceByUid(this._data, uid);
            }

            if (slot && slot.uid) {
                uid = [slot.uid];
            }

            this._updateSelection(slot, uid);
            this._adjustSelectedDate();
        },

        _updateSelection: function (dataItem, events) {
            var selection = this._selection;


            if (dataItem && selection) {
                var view = this.view();

                if (dataItem.uid) {
                    dataItem = view._updateEventForSelection(dataItem);
                }

                if (this._shiftKey && selection.start && selection.end) {
                    var backward = dataItem.end < selection.end;

                    selection.end = dataItem.endDate ? dataItem.endDate() : dataItem.end;

                    if (backward && view._timeSlotInterval) {
                        kendo.date.setTime(selection.end, -view._timeSlotInterval());
                    }
                } else {
                    selection.start = dataItem.startDate ? dataItem.startDate() : dataItem.start;
                    selection.end = dataItem.endDate ? dataItem.endDate() : dataItem.end;
                }

                if ("isDaySlot" in dataItem) {
                    selection.isAllDay = dataItem.isDaySlot;
                } else {
                    selection.isAllDay = dataItem.isAllDay;
                }

                selection.index = dataItem.index;
                if (this._ctrlKey) {
                    selection.events = selection.events.concat(events || []);
                } else {
                    selection.events = events || [];
                }
            }
        },

        options: {
            name: "Scheduler",
            date: TODAY,
            editable: true,
            autoBind: true,
            snap: true,
            mobile: false,
            timezone: "",
            allDaySlot: true,
            min: new Date(1900, 0, 1),
            max: new Date(2099, 11, 31),
            toolbar: null,
            messages: {
                today: "Today",
                pdf: "Export to PDF",
                save: "Save",
                cancel: "Cancel",
                destroy: "Delete",
                deleteWindowTitle: "Delete event",
                ariaSlotLabel: "Selected from {0:t} to {1:t}",
                ariaEventLabel: "{0} on {1:D} at {2:t}",
                views: {
                    day: "Day",
                    week: "Week",
                    workWeek: "Work Week",
                    agenda: "Agenda",
                    month: "Month",
                    timeline: "Timeline",
                    timelineWeek: "Timeline Week",
                    timelineWorkWeek: "Timeline Work Week",
                    timelineMonth: "Timeline Month"
                },
                recurrenceMessages: {
                    deleteWindowTitle: "Delete Recurring Item",
                    deleteWindowOccurrence: "Delete current occurrence",
                    deleteWindowSeries: "Delete the series",
                    editWindowTitle: "Edit Recurring Item",
                    editWindowOccurrence: "Edit current occurrence",
                    editWindowSeries: "Edit the series"
                },
                editor: {
                    title: "Title",
                    start: "Start",
                    end: "End",
                    allDayEvent: "All day event",
                    description: "Description",
                    repeat: "Repeat",
                    timezone: " ",
                    startTimezone: "Start timezone",
                    endTimezone: "End timezone",
                    separateTimezones: "Use separate start and end time zones",
                    timezoneEditorTitle: "Timezones",
                    timezoneEditorButton: "Time zone",
                    timezoneTitle: "Time zones",
                    noTimezone: "No timezone",
                    editorTitle: "Event"
                }
            },
            height: null,
            width: null,
            resources: [],
            group: {
                resources: [],
                direction: "horizontal"
            },
            views: [],
            selectable: false
        },

        events: [
            REMOVE,
            EDIT,
            CANCEL,
            SAVE,
            "add",
            "dataBinding",
            "dataBound",
            "moveStart",
            "move",
            "moveEnd",
            "resizeStart",
            "resize",
            "resizeEnd",
            "navigate",
            "change"
        ],

        destroy: function () {
            var that = this,
                element;

            Widget.fn.destroy.call(that);

            if (that.dataSource) {
                that.dataSource.unbind(CHANGE, that._refreshHandler);
                that.dataSource.unbind("progress", that._progressHandler);
                that.dataSource.unbind("error", that._errorHandler);
            }

            if (that.calendar) {
                that.calendar.destroy();
                that.popup.destroy();
            }

            if (that.view()) {
                that.view().destroy();
            }

            if (that._editor) {
                that._editor.destroy();
            }

            if (this._moveDraggable) {
                this._moveDraggable.destroy();
            }

            if (this._resizeDraggable) {
                this._resizeDraggable.destroy();
            }

            element = that.element
                .add(that.wrapper)
                .add(that.toolbar)
                .add(that.popup);

            element.off(NS);

            clearTimeout(that._moveTimer);

            that._model = null;
            that.toolbar = null;
            that.element = null;

            $(window).off("resize" + NS, that._resizeHandler);

            kendo.destroy(that.wrapper);
        },

        setDataSource: function (dataSource) {
            this.options.dataSource = dataSource;

            this._dataSource();

            if (this.options.autoBind) {
                dataSource.fetch();
            }
        },

        items: function () {
            return this.wrapper.find(".k-event, .k-task");
        },

        _movable: function () {
            var startSlot;
            var endSlot;
            var startTime;
            var endTime;
            var event;
            var clonedEvent;
            var that = this;

            var isMobile = that._isMobile();
            var movable = that.options.editable && that.options.editable.move !== false;
            var resizable = that.options.editable && that.options.editable.resize !== false;

            if (movable || (resizable && isMobile)) {

                that._moveDraggable = new kendo.ui.Draggable(that.element, {
                    distance: 0,
                    filter: ".k-event",
                    ignore: ".k-resize-handle",
                    holdToDrag: isMobile
                });

                if (movable) {
                    that._moveDraggable.bind("dragstart", function (e) {
                        var view = that.view();
                        var eventElement = e.currentTarget;

                        if (!view.options.editable || view.options.editable.move === false) {
                            e.preventDefault();
                            return;
                        }

                        if (isMobile && !eventElement.hasClass("k-event-active")) {
                            that.element.find(".k-event-active").removeClass("k-event-active");
                            e.preventDefault();
                            return;
                        }

                        event = that.occurrenceByUid(eventElement.attr(kendo.attr("uid")));

                        clonedEvent = event.clone();

                        view._updateEventForMove(clonedEvent);

                        startSlot = view._slotByPosition(e.x.startLocation, e.y.startLocation);

                        startTime = startSlot.startOffset(e.x.startLocation, e.y.startLocation, that.options.snap);

                        endSlot = startSlot;

                        if (!startSlot || that.trigger("moveStart", { event: event })) {
                            e.preventDefault();
                        }
                    })
                    .bind("drag", function (e) {
                        var view = that.view();

                        var slot = view._slotByPosition(e.x.location, e.y.location);

                        if (!slot) {
                            return;
                        }

                        endTime = slot.startOffset(e.x.location, e.y.location, that.options.snap);

                        var distance = endTime - startTime;

                        view._updateMoveHint(clonedEvent, slot.groupIndex, distance);

                        var range = moveEventRange(clonedEvent, distance);

                        if (!that.trigger("move", {
                            event: event,
                            slot: { element: slot.element, start: slot.startDate(), end: slot.endDate() },
                            resources: view._resourceBySlot(slot),
                            start: range.start,
                            end: range.end
                        })) {

                            endSlot = slot;

                        } else {
                            view._updateMoveHint(clonedEvent, slot.groupIndex, distance);
                        }
                    })
                    .bind("dragend", function (e) {
                        that.view()._removeMoveHint();

                        var distance = endTime - startTime;
                        var range = moveEventRange(clonedEvent, distance);

                        var start = range.start;
                        var end = range.end;

                        var endResources = that.view()._resourceBySlot(endSlot);
                        var startResources = that.view()._resourceBySlot(startSlot);

                        var prevented = that.trigger("moveEnd", {
                            event: event,
                            slot: { element: endSlot.element, start: endSlot.startDate(), end: endSlot.endDate() },
                            start: start,
                            end: end,
                            resources: endResources
                        });

                        if (!prevented && (clonedEvent.start.getTime() != start.getTime() ||
                        clonedEvent.end.getTime() != end.getTime() || kendo.stringify(endResources) != kendo.stringify(startResources))) {
                            that.view()._updateEventForMove(event);
                            that._updateEvent(null, event, $.extend({ start: start, end: end }, endResources));
                        }

                        e.currentTarget.removeClass("k-event-active");
                        this.cancelHold();
                    })
                    .bind("dragcancel", function () {
                        that.view()._removeMoveHint();
                        this.cancelHold();
                    });
                }

                if (isMobile) {
                    that._moveDraggable.bind("hold", function (e) {
                        if (that.element.find(".k-scheduler-monthview").length) {
                            e.preventDefault();
                        }
                        that.element.find(".k-event-active").removeClass("k-event-active");
                        e.currentTarget.addClass("k-event-active");
                    });

                    that._moveDraggable.userEvents.bind("press", function (e) {
                        e.preventDefault();
                    });
                }
            }
        },

        _resizable: function () {
            var startTime;
            var endTime;
            var event;
            var clonedEvent;
            var slot;
            var that = this;

            function direction(handle) {
                var directions = {
                    "k-resize-e": "east",
                    "k-resize-w": "west",
                    "k-resize-n": "north",
                    "k-resize-s": "south"
                };

                for (var key in directions) {
                    if (handle.hasClass(key)) {
                        return directions[key];
                    }
                }
            }

            that._resizeDraggable = new kendo.ui.Draggable(that.element, {
                distance: 0,
                filter: ".k-resize-handle",
                dragstart: function (e) {
                    var dragHandle = $(e.currentTarget);

                    var eventElement = dragHandle.closest(".k-event");

                    var uid = eventElement.attr(kendo.attr("uid"));

                    var view = that.view();

                    event = that.occurrenceByUid(uid);

                    clonedEvent = event.clone();

                    view._updateEventForResize(clonedEvent);

                    slot = view._slotByPosition(e.x.startLocation, e.y.startLocation);

                    if (that.trigger("resizeStart", { event: event })) {
                        e.preventDefault();
                    }

                    startTime = kendo.date.toUtcTime(clonedEvent.start);

                    endTime = kendo.date.toUtcTime(clonedEvent.end);
                },
                drag: function (e) {
                    if (!slot) {
                        return;
                    }

                    var dragHandle = $(e.currentTarget);

                    var dir = direction(dragHandle);

                    var view = that.view();

                    var currentSlot = view._slotByPosition(e.x.location, e.y.location);

                    if (!currentSlot || slot.groupIndex != currentSlot.groupIndex) {
                        return;
                    }

                    slot = currentSlot;

                    var originalStart = startTime;

                    var originalEnd = endTime;

                    if (dir == "south") {
                        if (!slot.isDaySlot && slot.end - kendo.date.toUtcTime(clonedEvent.start) >= view._timeSlotInterval()) {
                            if (clonedEvent.isAllDay) {
                                endTime = slot.startOffset(e.x.location, e.y.location, that.options.snap);
                            } else {
                                endTime = slot.endOffset(e.x.location, e.y.location, that.options.snap);
                            }
                        }
                    } else if (dir == "north") {
                        if (!slot.isDaySlot && kendo.date.toUtcTime(clonedEvent.end) - slot.start >= view._timeSlotInterval()) {
                            startTime = slot.startOffset(e.x.location, e.y.location, that.options.snap);
                        }
                    } else if (dir == "east") {
                        if (slot.isDaySlot && kendo.date.toUtcTime(kendo.date.getDate(slot.endDate())) >= kendo.date.toUtcTime(kendo.date.getDate(clonedEvent.start))) {
                            if (clonedEvent.isAllDay) {
                                endTime = slot.startOffset(e.x.location, e.y.location, that.options.snap);
                            } else {
                                endTime = slot.endOffset(e.x.location, e.y.location, that.options.snap);
                            }
                        } else if (!slot.isDaySlot && slot.end - kendo.date.toUtcTime(clonedEvent.start) >= view._timeSlotInterval()) {
                            endTime = slot.endOffset(e.x.location, e.y.location, that.options.snap);
                        }
                    } else if (dir == "west") {
                        if (slot.isDaySlot && kendo.date.toUtcTime(kendo.date.getDate(clonedEvent.end)) >= kendo.date.toUtcTime(kendo.date.getDate(slot.startDate()))) {
                            startTime = slot.startOffset(e.x.location, e.y.location, that.options.snap);
                        } else if (!slot.isDaySlot && kendo.date.toUtcTime(clonedEvent.end) - slot.start >= view._timeSlotInterval()) {
                            startTime = slot.startOffset(e.x.location, e.y.location, that.options.snap);
                        }
                    }

                    if (!that.trigger("resize", {
                        event: event,
                        slot: { element: slot.element, start: slot.startDate(), end: slot.endDate() },
                        start: kendo.timezone.toLocalDate(startTime),
                        end: kendo.timezone.toLocalDate(endTime),
                        resources: view._resourceBySlot(slot)
                    })) {
                        view._updateResizeHint(clonedEvent, slot.groupIndex, startTime, endTime);
                    } else {
                        startTime = originalStart;
                        endTime = originalEnd;
                    }
                },
                dragend: function (e) {
                    var dragHandle = $(e.currentTarget);
                    var start = new Date(clonedEvent.start.getTime());
                    var end = new Date(clonedEvent.end.getTime());
                    var dir = direction(dragHandle);

                    that.view()._removeResizeHint();

                    if (dir == "south") {
                        end = kendo.timezone.toLocalDate(endTime);
                    } else if (dir == "north") {
                        start = kendo.timezone.toLocalDate(startTime);
                    } else if (dir == "east") {
                        if (slot.isDaySlot) {
                            end = kendo.date.getDate(kendo.timezone.toLocalDate(endTime));
                        } else {
                            end = kendo.timezone.toLocalDate(endTime);
                        }
                    } else if (dir == "west") {
                        if (slot.isDaySlot) {
                            start = new Date(kendo.timezone.toLocalDate(startTime));
                            start.setHours(0);
                            start.setMinutes(0);
                        } else {
                            start = kendo.timezone.toLocalDate(startTime);
                        }
                    }

                    var prevented = that.trigger("resizeEnd", {
                        event: event,
                        slot: { element: slot.element, start: slot.startDate(), end: slot.endDate() },
                        start: start,
                        end: end,
                        resources: that.view()._resourceBySlot(slot)
                    });

                    if (!prevented && end.getTime() >= start.getTime()) {
                        if (clonedEvent.start.getTime() != start.getTime() || clonedEvent.end.getTime() != end.getTime()) {
                            that.view()._updateEventForResize(event);
                            that._updateEvent(dir, event, { start: start, end: end });
                        }
                    }

                    slot = null;
                    event = null;
                },
                dragcancel: function () {
                    that.view()._removeResizeHint();

                    slot = null;
                    event = null;
                }
            });
        },

        _updateEvent: function (dir, event, eventInfo) {
            var that = this;

            var updateEvent = function (event, callback) {
                try {
                    that._preventRefresh = true;
                    event.update(eventInfo);
                    that._convertDates(event);
                } finally {
                    that._preventRefresh = false;
                }

                if (!that.trigger(SAVE, { event: event })) {
                    if (callback) {
                        callback();
                    }

                    that._updateSelection(event);
                    that.dataSource.sync();
                }
            };

            var recurrenceHead = function (event) {
                if (event.recurrenceRule) {
                    return that.dataSource.getByUid(event.uid);
                } else {
                    return that.dataSource.get(event.recurrenceId);
                }
            };

            var updateSeries = function () {
                var head = recurrenceHead(event);

                if (dir == "south" || dir == "north") {
                    if (eventInfo.start) {
                        var start = kendo.date.getDate(head.start);
                        kendo.date.setTime(start, getMilliseconds(eventInfo.start));
                        eventInfo.start = start;
                    }
                    if (eventInfo.end) {
                        var end = kendo.date.getDate(head.end);
                        kendo.date.setTime(end, getMilliseconds(eventInfo.end));
                        eventInfo.end = end;
                    }
                }

                that.dataSource._removeExceptions(head);

                updateEvent(head);
            };

            var updateOccurrence = function () {
                var head = recurrenceHead(event);

                var callback = function () {
                    that._convertDates(head);
                };

                var exception = head.toOccurrence({ start: event.start, end: event.end });
                updateEvent(that.dataSource.add(exception), callback);
            };

            var recurrenceMessages = that.options.messages.recurrenceMessages;
            if (event.recurrenceRule || event.isOccurrence()) {
                that.showDialog({
                    model: event,
                    title: recurrenceMessages.editWindowTitle,
                    text: recurrenceMessages.editRecurring ? recurrenceMessages.editRecurring : EDITRECURRING,
                    buttons: [
                        { text: recurrenceMessages.editWindowOccurrence, click: updateOccurrence },
                        { text: recurrenceMessages.editWindowSeries, click: updateSeries }
                    ]
                });
            } else {
                updateEvent(that.dataSource.getByUid(event.uid));
            }
        },

        _modelForContainer: function (container) {
            container = $(container).closest("[" + kendo.attr("uid") + "]");

            return this.dataSource.getByUid(container.attr(kendo.attr("uid")));
        },

        showDialog: function (options) {
            this._editor.showDialog(options);
        },

        focus: function () {
            this.wrapper.focus();
        },

        _confirmation: function (callback, model) {
            var editable = this.options.editable;

            if (editable === true || editable.confirmation !== false) {
                var messages = this.options.messages;

                var text = typeof editable.confirmation === STRING ? editable.confirmation : DELETECONFIRM;
                var buttons = [
                    { name: "destroy", text: messages.destroy, click: function () { callback(); } }
                ];

                if (!(this._isMobile() && kendo.mobile.ui.Pane)) {
                    buttons.push({ name: "canceledit", text: messages.cancel, click: function () { callback(true); } });
                }

                this.showDialog({
                    model: model,
                    text: text,
                    title: messages.deleteWindowTitle,
                    buttons: buttons
                });
            } else {
                callback();
            }
        },

        addEvent: function (eventInfo) {
            var editable = this._editor.editable;
            var dataSource = this.dataSource;
            var event;

            eventInfo = eventInfo || {};

            var prevented = this.trigger("add", { event: eventInfo });

            if (!prevented && ((editable && editable.end()) || !editable)) {

                this.cancelEvent();

                if (eventInfo && eventInfo.toJSON) {
                    eventInfo = eventInfo.toJSON();
                }

                event = dataSource.add(eventInfo);

                if (event) {
                    this.cancelEvent();
                    this._editEvent(event);
                }
            }
        },

        saveEvent: function () {
            var editor = this._editor;

            if (!editor) {
                return;
            }

            var editable = editor.editable;
            var container = editor.container;
            var model = this._modelForContainer(container);

            if (container && editable && editable.end() &&
                !this.trigger(SAVE, { container: container, event: model })) {

                if (model.isRecurrenceHead()) {
                    this.dataSource._removeExceptions(model);
                }

                if (!model.dirty && !model.isOccurrence()) {
                    this._convertDates(model, "remove");
                }

                this.dataSource.sync();
            }
        },

        cancelEvent: function () {
            var editor = this._editor;
            var container = editor.container;
            var model;

            if (container) {
                model = this._modelForContainer(container);

                if (model && model.isOccurrence()) {
                    this._convertDates(model, "remove");
                    this._convertDates(this.dataSource.get(model.recurrenceId), "remove");
                }

                this.dataSource.cancelChanges(model);

                //TODO: handle the cancel in UI

                editor.close();
            }
        },

        editEvent: function (uid) {
            var model = typeof uid == "string" ? this.occurrenceByUid(uid) : uid;

            if (!model) {
                return;
            }

            this.cancelEvent();

            if (model.isRecurring()) {
                this._editRecurringDialog(model);
            } else {
                this._editEvent(model);
            }
        },

        _editEvent: function (model) {
            this._createPopupEditor(model);
        },

        _editRecurringDialog: function (model) {
            var that = this;

            var editOccurrence = function () {
                if (model.isException()) {
                    that._editEvent(model);
                } else {
                    that.addEvent(model);
                }
            };

            var editSeries = function () {
                if (model.recurrenceId) {
                    model = that.dataSource.get(model.recurrenceId);
                }

                that._editEvent(model);
            };

            var recurrenceMessages = that.options.messages.recurrenceMessages;
            that.showDialog({
                model: model,
                title: recurrenceMessages.editWindowTitle,
                text: recurrenceMessages.editRecurring ? recurrenceMessages.editRecurring : EDITRECURRING,
                buttons: [
                    { text: recurrenceMessages.editWindowOccurrence, click: editOccurrence },
                    { text: recurrenceMessages.editWindowSeries, click: editSeries }
                ]
            });
        },

        _createButton: function (command) {
            var template = command.template || COMMANDBUTTONTMPL,
                commandName = typeof command === STRING ? command : command.name || command.text,
                options = { className: "k-scheduler-" + (commandName || "").replace(/\s/g, ""), text: commandName, attr: "" };

            if (!commandName && !(isPlainObject(command) && command.template)) {
                throw new Error("Custom commands should have name specified");
            }

            if (isPlainObject(command)) {
                if (command.className) {
                    command.className += " " + options.className;
                }

                if (commandName === "edit" && isPlainObject(command.text)) {
                    command = extend(true, {}, command);
                    command.text = command.text.edit;
                }

                options = extend(true, options, defaultCommands[commandName], command);
            } else {
                options = extend(true, options, defaultCommands[commandName]);
            }

            return kendo.template(template)(options);
        },

        _convertDates: function (model, method) {
            var timezone = this.dataSource.reader.timezone;
            var startTimezone = model.startTimezone;
            var endTimezone = model.endTimezone;
            var start = model.start;
            var end = model.start;

            method = method || "apply";
            startTimezone = startTimezone || endTimezone;
            endTimezone = endTimezone || startTimezone;

            if (startTimezone) {
                if (timezone) {
                    if (method === "apply") {
                        start = kendo.timezone.convert(model.start, timezone, startTimezone);
                        end = kendo.timezone.convert(model.end, timezone, endTimezone);
                    } else {
                        start = kendo.timezone.convert(model.start, startTimezone, timezone);
                        end = kendo.timezone.convert(model.end, endTimezone, timezone);
                    }
                } else {
                    start = kendo.timezone[method](model.start, startTimezone);
                    end = kendo.timezone[method](model.end, endTimezone);
                }

                model._set("start", start);
                model._set("end", end);
            }
        },

        _createEditor: function () {
            var that = this;

            var editor;

            if (this._isMobile() && kendo.mobile.ui.Pane) {
                editor = that._editor = new MobileEditor(this.wrapper, extend({}, this.options, {
                    target: this,
                    timezone: that.dataSource.reader.timezone,
                    resources: that.resources,
                    createButton: proxy(this._createButton, this)
                }));
            } else {
                editor = that._editor = new PopupEditor(this.wrapper, extend({}, this.options, {
                    target: this,
                    createButton: proxy(this._createButton, this),
                    timezone: that.dataSource.reader.timezone,
                    resources: that.resources
                }));
            }

            editor.bind("cancel", function (e) {
                if (that.trigger("cancel", { container: e.container, event: e.model })) {
                    e.preventDefault();
                    return;
                }
                that.cancelEvent();

                that.focus();
            });

            editor.bind("edit", function (e) {
                if (that.trigger(EDIT, { container: e.container, event: e.model })) {
                    e.preventDefault();
                }
            });

            editor.bind("save", function () {
                that.saveEvent();
            });

            editor.bind("remove", function (e) {
                that.removeEvent(e.model);
            });
        },

        _createPopupEditor: function (model) {
            var editor = this._editor;

            if (!model.isNew() || model.isOccurrence()) {
                if (model.isOccurrence()) {
                    this._convertDates(model.recurrenceId ? this.dataSource.get(model.recurrenceId) : model);
                }
                this._convertDates(model);
            }

            this.editable = editor.editEvent(model);
        },

        removeEvent: function (uid) {
            var that = this,
                model = typeof uid == "string" ? that.occurrenceByUid(uid) : uid;

            if (!model) {
                return;
            }

            if (model.isRecurring()) {
                that._deleteRecurringDialog(model);
            } else {
                that._confirmation(function (cancel) {
                    if (!cancel) {
                        that._removeEvent(model);
                    }
                }, model);
            }
        },

        occurrenceByUid: function (uid) {
            var occurrence = this.dataSource.getByUid(uid);
            if (!occurrence) {
                occurrence = getOccurrenceByUid(this._data, uid);
            }

            return occurrence;
        },

        occurrencesInRange: function (start, end) {
            return new kendo.data.Query(this._data).filter({
                logic: "or",
                filters: [
                    {
                        logic: "and",
                        filters: [
                            { field: "start", operator: "gte", value: start },
                            { field: "end", operator: "gte", value: start },
                            { field: "start", operator: "lt", value: end }
                        ]
                    },
                    {
                        logic: "and",
                        filters: [
                            { field: "start", operator: "lte", value: start },
                            { field: "end", operator: "gt", value: start }
                        ]
                    }
                ]
            }).toArray();
        },

        _removeEvent: function (model) {
            if (!this.trigger(REMOVE, { event: model })) {
                if (this.dataSource.remove(model)) {
                    this.dataSource.sync();
                }
            }
        },

        _deleteRecurringDialog: function (model) {
            var that = this;

            var currentModel = model;

            var deleteOccurrence = function () {
                var occurrence = currentModel.recurrenceId ? currentModel : currentModel.toOccurrence();
                var head = that.dataSource.get(occurrence.recurrenceId);

                that._convertDates(head);
                that._removeEvent(occurrence);
            };

            var deleteSeries = function () {
                if (currentModel.recurrenceId) {
                    currentModel = that.dataSource.get(currentModel.recurrenceId);
                }

                that._removeEvent(currentModel);
            };

            var recurrenceMessages = that.options.messages.recurrenceMessages;
            that.showDialog({
                model: model,
                title: recurrenceMessages.deleteWindowTitle,
                text: recurrenceMessages.deleteRecurring ? recurrenceMessages.deleteRecurring : DELETERECURRING,
                buttons: [
                   { text: recurrenceMessages.deleteWindowOccurrence, click: deleteOccurrence },
                   { text: recurrenceMessages.deleteWindowSeries, click: deleteSeries }
                ]
            });
        },

        _unbindView: function (view) {
            var that = this;
            that.angular("cleanup", function () {
                return { elements: that.items() };
            });

            view.destroy();
        },

        _bindView: function (view) {
            var that = this;

            if (that.options.editable) {
                if (that._viewRemoveHandler) {
                    view.unbind(REMOVE, that._viewRemoveHandler);
                }

                that._viewRemoveHandler = function (e) {
                    that.removeEvent(e.uid);
                };

                view.bind(REMOVE, that._viewRemoveHandler);

                if (that._viewAddHandler) {
                    view.unbind(ADD, that._viewAddHandler);
                }

                that._viewAddHandler = function (e) {
                    that.addEvent(e.eventInfo);
                };

                view.bind(ADD, this._viewAddHandler);

                if (that._viewEditHandler) {
                    view.unbind(EDIT, that._viewEditHandler);
                }

                that._viewEditHandler = function (e) {
                    that.editEvent(e.uid);
                };

                view.bind(EDIT, this._viewEditHandler);
            }

            if (that._viewNavigateHandler) {
                view.unbind("navigate", that._viewNavigateHandler);
            }

            that._viewNavigateHandler = function (e) {
                if (e.view) {
                    var switchWorkDay = "isWorkDay" in e;
                    var action = switchWorkDay ? "changeWorkDay" : "changeView";

                    if (!that.trigger("navigate", { view: e.view, isWorkDay: e.isWorkDay, action: action, date: e.date })) {
                        if (switchWorkDay) {
                            that._workDayMode = e.isWorkDay;
                        }

                        that._selectView(e.view);
                        that.date(e.date);
                    }
                }
            };

            view.bind("navigate", that._viewNavigateHandler);

            if (that._viewActivateHandler) {
                view.unbind("activate", that._viewActivateHandler);
            }

            that._viewActivateHandler = function () {
                var view = this;
                if (that._selection) {
                    view.constrainSelection(that._selection);
                    that._select();

                    that._adjustSelectedDate();
                }
            };

            view.bind("activate", that._viewActivateHandler);
        },

        _selectView: function (name) {
            var that = this;

            if (name && that.views[name]) {

                if (that._selectedView) {
                    that._unbindView(that._selectedView);
                }

                that._selectedView = that._renderView(name);
                that._selectedViewName = name;

                that.toolbar
                    .find(".k-scheduler-views li")
                    .removeClass("k-state-selected")
                    .end()
                    .find(".k-view-" + name.replace(/\./g, "\\.").toLowerCase())
                    .addClass("k-state-selected");
            }
        },

        view: function (name) {
            var that = this;

            if (name) {

                that._selectView(name);

                that.rebind();

                return;
            }

            return that._selectedView;
        },

        _renderView: function (name) {
            var view = this._initializeView(name);

            this._bindView(view);

            this._model.set("formattedDate", view.dateForTitle());

            return view;
        },

        resize: function (force) {
            var size = this.getSize();
            var currentSize = this._size;
            var view = this.view();

            if (!view || !view.groups) {
                return;
            }

            if (force || !currentSize || size.width !== currentSize.width || size.height !== currentSize.height) {
                this.refresh({ action: "resize" });
                this._size = size;
            }
        },

        _adjustSelectedDate: function () {
            var date = this._model.selectedDate,
                selection = this._selection,
                start = selection.start;

            if (start && !kendo.date.isInDateRange(date, getDate(start), getDate(selection.end))) {
                date.setFullYear(start.getFullYear(), start.getMonth(), start.getDate());
            }
        },

        _initializeView: function (name) {
            var view = this.views[name];

            if (view) {
                var isSettings = isPlainObject(view),
                    type = view.type;

                if (typeof type === STRING) {
                    type = kendo.getter(view.type)(window);
                }

                if (type) {
                    view = new type(this.wrapper, trimOptions(extend(true, {}, this.options, isSettings ? view : {}, { resources: this.resources, date: this.date(), showWorkHours: this._workDayMode })));
                } else {
                    throw new Error("There is no such view");
                }
            }

            return view;
        },

        _views: function () {
            var views = this.options.views;
            var view;
            var defaultView;
            var selected;
            var isSettings;
            var name;
            var type;
            var idx;
            var length;

            this.views = {};

            for (idx = 0, length = views.length; idx < length; idx++) {
                var hasType = false;

                view = views[idx];

                isSettings = isPlainObject(view);

                if (isSettings) {
                    type = name = view.type ? view.type : view;
                    if (typeof type !== STRING) {
                        name = view.title;
                        hasType = true;
                    }
                } else {
                    type = name = view;
                }

                defaultView = defaultViews[name];

                if (defaultView && !hasType) {

                    view.type = defaultView.type;
                    defaultView.title = this.options.messages.views[name];
                    if (defaultView.type === "day") {
                        defaultView.messages = { allDay: this.options.messages.allDay };
                    } else if (defaultView.type === "agenda") {
                        defaultView.messages = {
                            event: this.options.messages.event,
                            date: this.options.messages.date,
                            time: this.options.messages.time
                        };
                    }
                }

                view = extend({ title: name }, defaultView, isSettings ? view : {});

                if (name) {
                    this.views[name] = view;

                    if (!selected || view.selected) {
                        selected = name;
                    }
                }
            }

            if (selected) {
                this._selectedViewName = selected; // toolbar is not rendered yet
            }
        },

        rebind: function () {
            this.dataSource.fetch();
        },

        _dataSource: function () {
            var that = this,
                options = that.options,
                dataSource = options.dataSource;

            dataSource = isArray(dataSource) ? { data: dataSource} : dataSource;

            if (options.timezone && !(dataSource instanceof SchedulerDataSource)) {
                dataSource = extend(true, dataSource, { schema: { timezone: options.timezone} });
            } else if (dataSource instanceof SchedulerDataSource) {
                options.timezone = dataSource.schema ? dataSource.schema.timezone : "";
            }

            if (that.dataSource && that._refreshHandler) {
                that.dataSource
                    .unbind(CHANGE, that._refreshHandler)
                    .unbind("progress", that._progressHandler)
                    .unbind("error", that._errorHandler);
            } else {
                that._refreshHandler = proxy(that.refresh, that);
                that._progressHandler = proxy(that._requestStart, that);
                that._errorHandler = proxy(that._error, that);
            }

            that.dataSource = kendo.data.SchedulerDataSource.create(dataSource)
                .bind(CHANGE, that._refreshHandler)
                .bind("progress", that._progressHandler)
                .bind("error", that._errorHandler);

            that.options.dataSource = that.dataSource;
        },

        _error: function () {
            this._progress(false);
        },

        _requestStart: function () {
            this._progress(true);
        },

        _progress: function (toggle) {
            var element = this.element.find(".k-scheduler-content");
            kendo.ui.progress(element, toggle);
        },

        _resources: function () {
            var that = this;
            var resources = that.options.resources;

            for (var idx = 0; idx < resources.length; idx++) {
                var resource = resources[idx];
                var field = resource.field;
                var dataSource = resource.dataSource;

                if (!field || !dataSource) {
                    throw new Error('The "field" and "dataSource" options of the scheduler resource are mandatory.');
                }

                that.resources.push({
                    field: field,
                    name: resource.name || field,
                    title: resource.title || field,
                    dataTextField: resource.dataTextField || "text",
                    dataValueField: resource.dataValueField || "value",
                    dataColorField: resource.dataColorField || "color",
                    valuePrimitive: resource.valuePrimitive != null ? resource.valuePrimitive : true,
                    multiple: resource.multiple || false,
                    dataSource: kendo.data.DataSource.create(dataSource)
                });
            }

            var promises = $.map(that.resources, function (resource) {
                return resource.dataSource.fetch();
            });

            $.when.apply(null, promises)
                  .then(function () {
                      if (that.options.autoBind) {
                          that.view(that._selectedViewName);
                      } else {
                          that._selectView(that._selectedViewName);
                      }
                  });
        },

        _initModel: function () {
            var that = this;
            that._model = kendo.observable({
                selectedDate: new Date(this.options.date),
                formattedDate: ""
            });

            that._model.bind("change", function (e) {
                if (e.field === "selectedDate") {
                    that.view(that._selectedViewName);
                }
            });
        },

        _wrapper: function () {
            var that = this;
            var options = that.options;
            var height = options.height;
            var width = options.width;

            that.wrapper = that.element
                               .addClass("k-widget k-scheduler k-floatwrap")
                               .attr("role", "grid")
                               .attr("aria-multiselectable", true);

            if (that._isMobile()) {
                that.wrapper.addClass("k-scheduler-mobile");
            }

            if (that._isMobilePhoneView()) {
                that.wrapper.addClass("k-scheduler-phone");
            }

            if (height) {
                that.wrapper.height(height);
            }

            if (width) {
                that.wrapper.width(width);
            }
        },

        date: function (value) {
            if (value != null && getDate(value) >= getDate(this.options.min) && getDate(value) <= getDate(this.options.max)) {
                this._model.set("selectedDate", value);
            }
            return getDate(this._model.get("selectedDate"));
        },

        _toolbar: function () {
            var that = this;
            var options = that.options;
            var commands = [];

            if (options.toolbar) {
                commands = $.isArray(options.toolbar) ? options.toolbar : [options.toolbar];
            }

            var template = this._isMobilePhoneView() ? MOBILETOOLBARTEMPLATE : TOOLBARTEMPLATE;
            var toolbar = $(template({
                messages: options.messages,
                pdf: $.grep(commands, function (item) {
                    return item == "pdf" || item.name == "pdf";
                }).length > 0,
                ns: kendo.ns,
                views: that.views
            }));

            that.wrapper.append(toolbar);
            that.toolbar = toolbar;

            kendo.bind(that.toolbar, that._model);

            toolbar.on(CLICK + NS, ".k-pdf", function (e) {
                e.preventDefault();
                that.saveAsPDF();
            });

            toolbar.on(CLICK + NS, ".k-scheduler-navigation li", function (e) {
                var li = $(this);
                var date = new Date(that.date());
                var action = "";

                e.preventDefault();

                if (li.hasClass("k-nav-today")) {
                    action = "today";
                    date = new Date();
                } else if (li.hasClass("k-nav-next")) {
                    action = "next";
                    date = that.view().nextDate();
                } else if (li.hasClass("k-nav-prev")) {
                    action = "previous";
                    date = that.view().previousDate();
                } else if (li.hasClass("k-nav-current") && !that._isMobilePhoneView()) {
                    that._showCalendar();
                    return; // TODO: Not good - refactor
                }

                if (!that.trigger("navigate", { view: that._selectedViewName, action: action, date: date })) {
                    that.date(date);
                }
            });

            toolbar.on(CLICK + NS, ".k-scheduler-views li", function (e) {
                e.preventDefault();

                var name = $(this).attr(kendo.attr("name"));

                if (!that.trigger("navigate", { view: name, action: "changeView", date: that.date() })) {
                    that.view(name);
                }
            });

            toolbar.find("li").hover(function () {
                $(this).addClass("k-state-hover");
            }, function () {
                $(this).removeClass("k-state-hover");
            });
        },

        _showCalendar: function () {
            var that = this,
                target = that.toolbar.find(".k-nav-current"),
                html = $('<div class="k-calendar-container"><div class="k-scheduler-calendar"/></div>');

            if (!that.popup) {
                that.popup = new Popup(html, {
                    anchor: target,
                    activate: function () {
                        if (!that.calendar) {
                            that.calendar = new Calendar(this.element.find(".k-scheduler-calendar"),
                            {
                                change: function () {
                                    var date = this.value();
                                    if (!that.trigger("navigate", { view: that._selectedViewName, action: "changeDate", date: date })) {
                                        that.date(date);
                                        that.popup.close();
                                    }
                                },
                                min: that.options.min,
                                max: that.options.max
                            });
                        }
                        that.calendar.value(that.date());
                    },
                    copyAnchorStyles: false
                });
            }

            that.popup.open();
        },

        refresh: function (e) {
            var that = this;
            var view = this.view();

            this._progress(false);

            this.angular("cleanup", function () {
                return { elements: that.items() };
            });

            e = e || {};

            if (!view) {
                return;
            }

            if (e && e.action === "itemchange" && (this._editor.editable || this._preventRefresh)) { // skip rebinding if editing is in progress
                return;
            }

            if (this.trigger("dataBinding", { action: e.action || "rebind", index: e.index, items: e.items })) {
                return;
            }

            if (!(e && e.action === "resize") && this._editor) {
                this._editor.close();
            }

            this._data = this.dataSource.expand(view.startDate(), view.endDate());

            view.render(this._data);

            this.trigger("dataBound");
        },

        slotByPosition: function (x, y) {
            var view = this.view();

            if (!view._slotByPosition) {
                return null;
            }

            var slot = view._slotByPosition(x, y);

            if (!slot) {
                return null;
            }

            return {
                startDate: slot.startDate(),
                endDate: slot.endDate(),
                groupIndex: slot.groupIndex,
                element: slot.element,
                isDaySlot: slot.isDaySlot
            };
        },

        slotByElement: function (element) {
            var offset = $(element).offset();
            return this.slotByPosition(offset.left, offset.top);
        },

        resourcesBySlot: function (slot) {
            return this.view()._resourceBySlot(slot);
        }
    });

    var defaultViews = {
        day: {
            type: "kendo.ui.DayView"
        },
        week: {
            type: "kendo.ui.WeekView"
        },
        workWeek: {
            type: "kendo.ui.WorkWeekView"
        },
        agenda: {
            type: "kendo.ui.AgendaView"
        },
        month: {
            type: "kendo.ui.MonthView"
        },
        timeline: {
            type: "kendo.ui.TimelineView"
        },
        timelineWeek: {
            type: "kendo.ui.TimelineWeekView"
        },
        timelineWorkWeek: {
            type: "kendo.ui.TimelineWorkWeekView"
        },
        timelineMonth: {
            type: "kendo.ui.TimelineMonthView"
        }
    };

    ui.plugin(Scheduler);

    if (kendo.PDFMixin) {
        kendo.PDFMixin.extend(Scheduler.prototype);
    }

    var TimezoneEditor = Widget.extend({
        init: function (element, options) {
            var that = this,
                zones = kendo.timezone.windows_zones;

            if (!zones || !kendo.timezone.zones_titles) {
                throw new Error('kendo.timezones.min.js is not included.');
            }

            Widget.fn.init.call(that, element, options);

            that.wrapper = that.element;

            that._zonesQuery = new kendo.data.Query(zones);
            that._zoneTitleId = kendo.guid();
            that._zoneTitlePicker();
            that._zonePicker();

            that.value(that.options.value);
        },
        options: {
            name: "TimezoneEditor",
            value: "",
            optionLabel: "No timezone"
        },
        events: ["change"],

        _zoneTitlePicker: function () {
            var that = this,
                zoneTitle = $('<input id="' + that._zoneTitleId + '"/>').appendTo(that.wrapper);

            that._zoneTitle = new kendo.ui.DropDownList(zoneTitle, {
                dataSource: kendo.timezone.zones_titles,
                dataValueField: "other_zone",
                dataTextField: "name",
                optionLabel: that.options.optionLabel,
                cascade: function () {
                    if (!this.value()) {
                        that._zone.wrapper.hide();
                    }
                }
            });
        },

        _zonePicker: function () {
            var that = this,
                zone = $('<input />').appendTo(this.wrapper);

            that._zone = new kendo.ui.DropDownList(zone, {
                dataValueField: "zone",
                dataTextField: "territory",
                dataSource: that._zonesQuery.data,
                cascadeFrom: that._zoneTitleId,
                cascade: function () {
                    that._value = this.value();
                    that.trigger("change");
                },
                dataBound: function () {
                    that._value = this.value();
                    this.wrapper.toggle(this.dataSource.view().length > 1);
                }
            });

            that._zone.wrapper.hide();
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            kendo.destroy(this.wrapper);
        },

        value: function (value) {
            var that = this,
                zone;

            if (value === undefined) {
                return that._value;
            }

            zone = that._zonesQuery.filter({ field: "zone", operator: "eq", value: value }).data[0];

            if (zone) {
                that._zoneTitle.value(zone.other_zone);
                that._zone.value(zone.zone);
            } else {
                that._zoneTitle.value("");
            }

        }
    });

    ui.plugin(TimezoneEditor);

    var ZONETITLEOPTIONTEMPLATE = kendo.template('<option value="#=other_zone#">#=name#</option>');
    var ZONEOPTIONTEMPLATE = kendo.template('<option value="#=zone#">#=territory#</option>');

    var MobileTimezoneEditor = Widget.extend({
        init: function (element, options) {
            var that = this,
                zones = kendo.timezone.windows_zones;

            if (!zones || !kendo.timezone.zones_titles) {
                throw new Error('kendo.timezones.min.js is not included.');
            }

            Widget.fn.init.call(that, element, options);

            that.wrapper = that.element;

            that._zonesQuery = new kendo.data.Query(zones);
            that._zoneTitlePicker();
            that._zonePicker();

            that.value(that.options.value);
        },

        options: {
            name: "MobileTimezoneEditor",
            optionLabel: "No timezone",
            value: ""
        },

        events: ["change"],

        _bindZones: function (value) {
            var data = value ? this._filter(value) : [];

            this._zone.html(this._options(data, ZONEOPTIONTEMPLATE));
        },

        _filter: function (value) {
            return this._zonesQuery.filter({ field: "other_zone", operator: "eq", value: value }).data;
        },

        _options: function (data, template, optionLabel) {
            var idx = 0;
            var html = "";
            var length = data.length;

            if (optionLabel) {
                html += template({ other_zone: "", name: optionLabel });
            }

            for (; idx < length; idx++) {
                html += template(data[idx]);
            }

            return html;
        },

        _zoneTitlePicker: function () {
            var that = this;
            var options = that._options(kendo.timezone.zones_titles, ZONETITLEOPTIONTEMPLATE, that.options.optionLabel);

            that._zoneTitle = $('<select>' + options + '</select>')
                                .appendTo(that.wrapper)
                                .change(function () {
                                    var value = this.value;
                                    var zone = that._zone;

                                    that._bindZones(value);

                                    if (value && zone[0].children.length > 1) {
                                        zone.show();
                                    } else {
                                        zone.hide();
                                    }

                                    that._value = zone[0].value;

                                    that.trigger("change");
                                });
        },

        _zonePicker: function () {
            var that = this;

            that._zone = $('<select style="display:none"></select>')
                            .appendTo(this.wrapper)
                            .change(function () {
                                that._value = this.value;

                                that.trigger("change");
                            });

            that._bindZones(that._zoneTitle.val());
            that._value = that._zone[0].value;
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            kendo.destroy(this.wrapper);
        },

        value: function (value) {
            var that = this;
            var zonePicker = that._zone;
            var other_zone = "";
            var zone_value = "";
            var zone;

            if (value === undefined) {
                return that._value;
            }

            zone = that._zonesQuery.filter({ field: "zone", operator: "eq", value: value }).data[0];

            if (zone) {
                zone_value = zone.zone;
                other_zone = zone.other_zone;
            }

            that._zoneTitle.val(other_zone);
            that._bindZones(other_zone);

            zonePicker.val(zone_value);
            zone_value = zonePicker[0].value;

            if (zone_value && zonePicker[0].children.length > 1) {
                zonePicker.show();
            } else {
                zonePicker.hide();
            }

            that._value = zone_value;
        }
    });

    ui.plugin(MobileTimezoneEditor);

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        proxy = $.proxy,
        abs = Math.abs,
        MAX_DOUBLE_TAP_DISTANCE = 20;

    var Swipe = kendo.Class.extend({
        init: function (element, callback, options) {
            options = $.extend({
                minXDelta: 30,
                maxYDelta: 20,
                maxDuration: 1000
            }, options);

            new kendo.UserEvents(element, {
                surface: options.surface,
                allowSelection: true,

                start: function (e) {
                    if (abs(e.x.velocity) * 2 >= abs(e.y.velocity)) {
                        e.sender.capture();
                    }
                },

                move: function (e) {
                    var touch = e.touch,
                    duration = e.event.timeStamp - touch.startTime,
                    direction = touch.x.initialDelta > 0 ? "right" : "left";

                    if (
                        abs(touch.x.initialDelta) >= options.minXDelta &&
                        abs(touch.y.initialDelta) < options.maxYDelta &&
                    duration < options.maxDuration) {
                        callback({
                            direction: direction,
                            touch: touch,
                            target: touch.target
                        });

                        touch.cancel();
                    }
                }
            });
        }
    });

    var Touch = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);
            options = that.options;

            element = that.element;

            function eventProxy(name) {
                return function (e) {
                    that._triggerTouch(name, e);
                };
            }

            function gestureEventProxy(name) {
                return function (e) {
                    that.trigger(name, { touches: e.touches, distance: e.distance, center: e.center, event: e.event });
                };
            }

            that.events = new kendo.UserEvents(element, {
                filter: options.filter,
                surface: options.surface,
                minHold: options.minHold,
                multiTouch: options.multiTouch,
                allowSelection: true,
                press: eventProxy("touchstart"),
                hold: eventProxy("hold"),
                tap: proxy(that, "_tap"),
                gesturestart: gestureEventProxy("gesturestart"),
                gesturechange: gestureEventProxy("gesturechange"),
                gestureend: gestureEventProxy("gestureend")
            });

            if (options.enableSwipe) {
                that.events.bind("start", proxy(that, "_swipestart"));
                that.events.bind("move", proxy(that, "_swipemove"));
            } else {
                that.events.bind("start", proxy(that, "_dragstart"));
                that.events.bind("move", eventProxy("drag"));
                that.events.bind("end", eventProxy("dragend"));
            }

            kendo.notify(that);
        },

        events: [
            "touchstart",
            "dragstart",
            "drag",
            "dragend",
            "tap",
            "doubletap",
            "hold",
            "swipe",
            "gesturestart",
            "gesturechange",
            "gestureend"
        ],

        options: {
            name: "Touch",
            surface: null,
            global: false,
            multiTouch: false,
            enableSwipe: false,
            minXDelta: 30,
            maxYDelta: 20,
            maxDuration: 1000,
            minHold: 800,
            doubleTapTimeout: 800
        },

        cancel: function () {
            this.events.cancel();
        },

        _triggerTouch: function (type, e) {
            if (this.trigger(type, { touch: e.touch, event: e.event })) {
                e.preventDefault();
            }
        },

        _tap: function (e) {
            var that = this,
                lastTap = that.lastTap,
                touch = e.touch;

            if (lastTap &&
                (touch.endTime - lastTap.endTime < that.options.doubleTapTimeout) &&
                kendo.touchDelta(touch, lastTap).distance < MAX_DOUBLE_TAP_DISTANCE
                ) {

                that._triggerTouch("doubletap", e);
                that.lastTap = null;
            } else {
                that._triggerTouch("tap", e);
                that.lastTap = touch;
            }
        },

        _dragstart: function (e) {
            this._triggerTouch("dragstart", e);
        },

        _swipestart: function (e) {
            if (abs(e.x.velocity) * 2 >= abs(e.y.velocity)) {
                e.sender.capture();
            }
        },

        _swipemove: function (e) {
            var that = this,
                options = that.options,
                touch = e.touch,
                duration = e.event.timeStamp - touch.startTime,
                direction = touch.x.initialDelta > 0 ? "right" : "left";

            if (
                abs(touch.x.initialDelta) >= options.minXDelta &&
                abs(touch.y.initialDelta) < options.maxYDelta &&
                duration < options.maxDuration
                ) {
                that.trigger("swipe", {
                    direction: direction,
                    touch: e.touch
                });

                touch.cancel();
            }
        }
    });


    window.jQuery.fn.kendoMobileSwipe = function (callback, options) {
        this.each(function () {
            new Swipe(this, callback, options);
        });
    };

    kendo.ui.plugin(Touch);
})(window.kendo.jQuery);



;

(function ($) {
    var kendo = window.kendo;
    var kendoDom = kendo.dom;
    var kendoDomElement = kendoDom.element;
    var kendoTextElement = kendoDom.text;
    var browser = kendo.support.browser;
    var mobileOS = kendo.support.mobileOS;
    var ui = kendo.ui;
    var Widget = ui.Widget;
    var extend = $.extend;
    var map = $.map;
    var isFunction = $.isFunction;
    var keys = kendo.keys;
    var titleFromField = {
        "title": "Title",
        "start": "Start Time",
        "end": "End Time",
        "percentComplete": "% Done",
        "parentId": "Predecessor ID",
        "id": "ID",
        "orderId": "Order ID"
    };
    var STRING = "string";
    var NS = ".kendoGanttList";
    var CLICK = "click";
    var DOT = ".";

    var listStyles = {
        wrapper: "k-treelist k-grid k-widget",
        header: "k-header",
        alt: "k-alt",
        editCell: "k-edit-cell",
        group: "k-treelist-group",
        gridHeader: "k-grid-header",
        gridHeaderWrap: "k-grid-header-wrap",
        gridContent: "k-grid-content",
        gridContentWrap: "k-grid-content",
        selected: "k-state-selected",
        icon: "k-icon",
        iconCollapse: "k-i-collapse",
        iconExpand: "k-i-expand",
        iconHidden: "k-i-none",
        iconPlaceHolder: "k-icon k-i-none",
        input: "k-input",
        dropPositions: "k-insert-top k-insert-bottom k-add k-insert-middle",
        dropTop: "k-insert-top",
        dropBottom: "k-insert-bottom",
        dropAdd: "k-add",
        dropMiddle: "k-insert-middle",
        dropDenied: "k-denied",
        dragStatus: "k-drag-status",
        dragClue: "k-drag-clue",
        dragClueText: "k-clue-text"
    };

    function createPlaceholders(options) {
        var spans = [];
        var className = options.className;

        for (var i = 0, level = options.level; i < level; i++) {
            spans.push(kendoDomElement("span", { className: className }));
        }

        return spans;
    }

    function blurActiveElement() {
        var activeElement = kendo._activeElement();

        if (activeElement.nodeName.toLowerCase() !== "body") {
            $(activeElement).blur();
        }
    }

    var GanttList = ui.GanttList = Widget.extend({
        init: function (element, options) {
            Widget.fn.init.call(this, element, options);

            if (this.options.columns.length === 0) {
                this.options.columns.push("title");
            }

            this.dataSource = this.options.dataSource;

            this._columns();
            this._layout();
            this._domTrees();
            this._header();
            this._sortable();
            this._editable();
            this._selectable();
            this._draggable();
            this._attachEvents();

            this._adjustHeight();
        },

        _adjustHeight: function () {
            this.content.height(this.element.height() - this.header.parent().outerHeight());
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            if (this._reorderDraggable) {
                this._reorderDraggable.destroy();
            }

            if (this._tableDropArea) {
                this._tableDropArea.destroy();
            }

            if (this._contentDropArea) {
                this._contentDropArea.destroy();
            }

            if (this.touch) {
                this.touch.destroy();
            }

            this.content.off(NS);
            this.header = null;
            this.content = null;
            this.levels = null;

            kendo.destroy(this.element);
        },

        options: {
            name: "GanttList",
            selectable: true,
            editable: true
        },

        _attachEvents: function () {
            var that = this;
            var listStyles = GanttList.styles;

            that.content
                .on(CLICK + NS, "td > span." + listStyles.icon + ":not(." + listStyles.iconHidden + ")", function (e) {
                    var element = $(this);
                    var model = that._modelFromElement(element);

                    model.set("expanded", !model.get("expanded"));

                    e.stopPropagation();
                });
        },

        _domTrees: function () {
            this.headerTree = new kendoDom.Tree(this.header[0]);
            this.contentTree = new kendoDom.Tree(this.content[0]);
        },

        _columns: function () {
            var columns = this.options.columns;
            var column;
            var model = function () {
                this.field = "";
                this.title = "";
                this.editable = false;
                this.sortable = false;
            };

            this.columns = map(columns, function (column) {
                column = typeof column === "string" ? {
                    field: column, title: titleFromField[column]
                } : column;

                return extend(new model(), column);
            });
        },

        _layout: function () {
            var element = this.element;
            var listStyles = GanttList.styles;

            element
                .addClass(listStyles.wrapper)
                .append("<div class='" + listStyles.gridHeader + "'><div class='" + listStyles.gridHeaderWrap + "'></div></div>")
                .append("<div class='" + listStyles.gridContentWrap + "'></div>");

            this.header = element.find(DOT + listStyles.gridHeaderWrap);
            this.content = element.find(DOT + listStyles.gridContent);
        },

        _header: function () {
            var domTree = this.headerTree;
            var colgroup;
            var thead;
            var table;

            colgroup = kendoDomElement("colgroup", null, this._cols());
            thead = kendoDomElement("thead", { "role": "rowgroup" }, [kendoDomElement("tr", { "role": "row" }, this._ths())]);
            table = kendoDomElement("table", {
                "style": { "min-width": this.options.listWidth + "px" },
                "role": "grid"
            }, [colgroup, thead]);

            domTree.render([table]);
        },

        _render: function (tasks) {
            var colgroup;
            var tbody;
            var table;

            this.levels = [{ field: null, value: 0}];

            colgroup = kendoDomElement("colgroup", null, this._cols());
            tbody = kendoDomElement("tbody", { "role": "rowgroup" }, this._trs(tasks));
            table = kendoDomElement("table", {
                "style": { "min-width": this.options.listWidth + "px" },
                "tabIndex": 0,
                "role": "treegrid"
            }, [colgroup, tbody]);

            this.contentTree.render([table]);
            this.trigger("render");
        },

        _ths: function () {
            var columns = this.columns;
            var column;
            var attr;
            var ths = [];

            for (var i = 0, length = columns.length; i < length; i++) {
                column = columns[i];
                attr = {
                    "data-field": column.field,
                    "data-title": column.title, className: GanttList.styles.header,
                    "role": "columnheader"
                };

                ths.push(kendoDomElement("th", attr, [kendoTextElement(column.title)]));
            }

            return ths;
        },

        _cols: function () {
            var columns = this.columns;
            var column;
            var style;
            var width;
            var cols = [];

            for (var i = 0, length = columns.length; i < length; i++) {
                column = columns[i];
                width = column.width;

                if (width && parseInt(width, 10) !== 0) {
                    style = { style: { width: typeof width === STRING ? width : width + "px"} };
                } else {
                    style = null;
                }

                cols.push(kendoDomElement("col", style, []));
            }

            return cols;
        },

        _trs: function (tasks) {
            var task;
            var rows = [];
            var attr;
            var className = [];
            var level;
            var listStyles = GanttList.styles;

            for (var i = 0, length = tasks.length; i < length; i++) {
                task = tasks[i];
                level = this._levels({
                    idx: task.parentId,
                    id: task.id,
                    summary: task.summary
                });

                attr = {
                    "data-uid": task.uid,
                    "data-level": level,
                    "role": "row"
                };

                if (task.summary) {
                    attr["aria-expanded"] = task.expanded;
                }

                if (i % 2 !== 0) {
                    className.push(listStyles.alt);
                }

                if (task.summary) {
                    className.push(listStyles.group);
                }

                if (className.length) {
                    attr.className = className.join(" ");
                }

                rows.push(this._tds({
                    task: task,
                    attr: attr,
                    level: level
                }));

                className = [];
            }

            return rows;
        },

        _tds: function (options) {
            var children = [];
            var columns = this.columns;
            var column;

            for (var i = 0, l = columns.length; i < l; i++) {
                column = columns[i];

                children.push(this._td({ task: options.task, column: column, level: options.level }));
            }

            return kendoDomElement("tr", options.attr, children);
        },

        _td: function (options) {
            var children = [];
            var resourcesField = this.options.resourcesField;
            var listStyles = GanttList.styles;
            var task = options.task;
            var column = options.column;
            var value = task.get(column.field) || [];
            var formatedValue;

            if (column.field == resourcesField) {
                formatedValue = [];
                for (var i = 0; i < value.length; i++) {
                    formatedValue.push(kendo.format("{0} [{1}]", value[i].get("name"), value[i].get("formatedValue")));
                }
                formatedValue = formatedValue.join(", ");
            } else {
                formatedValue = column.format ? kendo.format(column.format, value) : value;
            }

            if (column.field === "title") {
                children = createPlaceholders({ level: options.level, className: listStyles.iconPlaceHolder });
                children.push(kendoDomElement("span", {
                    className: listStyles.icon + " " + (task.summary ? (task.expanded ? listStyles.iconCollapse : listStyles.iconExpand)
                        : listStyles.iconHidden)
                }));
            }

            children.push(kendoDomElement("span", null, [kendoTextElement(formatedValue)]));

            return kendoDomElement("td", { "role": "gridcell" }, children);
        },

        _levels: function (options) {
            var levels = this.levels;
            var level;
            var summary = options.summary;
            var idx = options.idx;
            var id = options.id;

            for (var i = 0, length = levels.length; i < length; i++) {
                level = levels[i];

                if (level.field == idx) {

                    if (summary) {
                        levels.push({ field: id, value: level.value + 1 });
                    }

                    return level.value;
                }
            }
        },

        _sortable: function () {
            var resourcesField = this.options.resourcesField;
            var columns = this.columns;
            var column;
            var sortableInstance;
            var cells = this.header.find("th");
            var cell;

            for (var idx = 0, length = cells.length; idx < length; idx++) {
                column = columns[idx];

                if (column.sortable && column.field !== resourcesField) {
                    cell = cells.eq(idx);

                    sortableInstance = cell.data("kendoColumnSorter");

                    if (sortableInstance) {
                        sortableInstance.destroy();
                    }

                    cell.attr("data-" + kendo.ns + "field", column.field)
                        .kendoColumnSorter({ dataSource: this.dataSource });
                }
            }
            cells = null;
        },

        _selectable: function () {
            var that = this;
            var selectable = this.options.selectable;

            if (selectable) {
                this.content
                   .on(CLICK + NS, "tr", function (e) {
                       var element = $(this);

                       if (!e.ctrlKey) {
                           that.select(element);
                       } else {
                           that.clearSelection();
                       }
                   });
            }
        },

        select: function (value) {
            var element = this.content.find(value);
            var selectedClassName = GanttList.styles.selected;

            if (element.length) {
                element
                    .siblings(DOT + selectedClassName)
                    .removeClass(selectedClassName)
                    .attr("aria-selected", false)
                    .end()
                    .addClass(selectedClassName)
                    .attr("aria-selected", true);

                this.trigger("change");

                return;
            }

            return this.content.find(DOT + selectedClassName);
        },

        clearSelection: function () {
            var selected = this.select();

            if (selected.length) {
                selected.removeClass(GanttList.styles.selected);

                this.trigger("change");
            }
        },

        _setDataSource: function (dataSource) {
            this.dataSource = dataSource;
        },

        _editable: function () {
            var that = this;
            var listStyles = GanttList.styles;
            var iconSelector = "span." + listStyles.icon + ":not(" + listStyles.iconHidden + ")";
            var finishEdit = function () {
                if (that.editable && that.editable.end()) {
                    that._closeCell();
                }
            };
            var mousedown = function (e) {
                var currentTarget = $(e.currentTarget);

                if (!currentTarget.hasClass(listStyles.editCell)) {
                    blurActiveElement();
                }
            };

            if (!this.options.editable) {
                return;
            }

            this._startEditHandler = function (e) {
                var td = e.currentTarget ? $(e.currentTarget) : e;
                var column = that._columnFromElement(td);

                if (that.editable) {
                    return;
                }

                if (column.editable) {
                    that._editCell({ cell: td, column: column });
                }
            };

            that.content
                .on("focusin" + NS, function () {
                    clearTimeout(that.timer);
                    that.timer = null;
                })
                .on("focusout" + NS, function () {
                    that.timer = setTimeout(finishEdit, 1);
                })
                .on("keydown" + NS, function (e) {
                    var key = e.keyCode;
                    var cell;
                    var model;

                    switch (key) {
                        case keys.ENTER:
                            blurActiveElement();
                            finishEdit();
                            break;
                        case keys.ESC:
                            if (that.editable) {
                                cell = that._editableContainer;
                                model = that._modelFromElement(cell);
                                if (!that.trigger("cancel", { model: model, cell: cell })) {
                                    that._closeCell(true);
                                }
                            }
                            break;
                    }
                });

            if (!mobileOS) {
                that.content
                    .on("mousedown" + NS, "td", function (e) {
                        mousedown(e);
                    })
                    .on("dblclick" + NS, "td", function (e) {
                        if (!$(e.target).is(iconSelector)) {
                            that._startEditHandler(e);
                        }
                    });
            } else {
                that.touch = that.content
                    .kendoTouch({
                        filter: "td",
                        touchstart: function (e) {
                            mousedown(e.touch);
                        },
                        doubletap: function (e) {
                            if (!$(e.touch.initialTouch).is(iconSelector)) {
                                that._startEditHandler(e.touch);
                            }
                        }
                    }).data("kendoTouch");
            }
        },

        _editCell: function (options) {
            var resourcesField = this.options.resourcesField;
            var listStyles = GanttList.styles;
            var cell = options.cell;
            var column = options.column;
            var model = this._modelFromElement(cell);
            var modelCopy = this.dataSource._createNewModel(model.toJSON());
            var field = modelCopy.fields[column.field] || modelCopy[column.field];
            var validation = field.validation;
            var DATATYPE = kendo.attr("type");
            var BINDING = kendo.attr("bind");
            var attr = {
                "name": column.field,
                "required": field.validation ?
                    field.validation.required === true : false
            };
            var editor;

            if (column.field === resourcesField) {
                column.editor(cell, modelCopy);
                return;
            }

            this._editableContent = cell.children().detach();
            this._editableContainer = cell;

            cell.data("modelCopy", modelCopy);

            if ((field.type === "date" || $.type(field) === "date") &&
                /H|m|s|F|g|u/.test(column.format)) {
                if (column.field === "start") {
                    delete field.validation.dateCompare;
                }

                attr[BINDING] = "value:" + column.field;
                attr[DATATYPE] = "date";
                editor = function (container, options) {
                    $('<input type="text"/>').attr(attr)
                        .appendTo(container).kendoDateTimePicker({ format: options.format });
                };
            }

            this.editable = cell
                .addClass(listStyles.editCell)
                .kendoEditable({
                    fields: {
                        field: column.field,
                        format: column.format,
                        editor: column.editor || editor
                    },
                    model: modelCopy,
                    clearContainer: false
                }).data("kendoEditable");

            if (validation && validation.dateCompare &&
                isFunction(validation.dateCompare) && validation.message) {
                $('<span ' + kendo.attr("for") + '="' + column.field + '" class="k-invalid-msg"/>')
                    .hide()
                    .appendTo(cell);

                cell.find('[name=' + column.field + ']')
                    .attr(kendo.attr("dateCompare-msg"), validation.message);
            }

            if (this.trigger("edit", { model: model, cell: cell })) {
                this._closeCell(true);
            }
        },

        _closeCell: function (cancelUpdate) {
            var listStyles = GanttList.styles;
            var cell = this._editableContainer;
            var model = this._modelFromElement(cell);
            var column = this._columnFromElement(cell);
            var field = column.field;
            var copy = cell.data("modelCopy");
            var taskInfo = {};

            taskInfo[field] = copy.get(field);

            cell.empty()
                .removeData("modelCopy")
                .removeClass(listStyles.editCell)
                .append(this._editableContent);

            this.editable.destroy();
            this.editable = null;

            this._editableContainer = null;
            this._editableContent = null;

            if (!cancelUpdate) {
                if (field === "start") {
                    taskInfo.end = new Date(taskInfo.start.getTime() + model.duration());
                }

                this.trigger("update", { task: model, updateInfo: taskInfo });
            }
        },

        _draggable: function () {
            var that = this;
            var draggedTask = null;
            var dropAllowed = true;
            var dropTarget;
            var listStyles = GanttList.styles;
            var selector = 'tr[' + kendo.attr("level") + ' = 0]:last';
            var action = {};
            var clear = function () {
                draggedTask = null;
                dropTarget = null;
                dropAllowed = true;
                action = {};
            };
            var allowDrop = function (task) {
                var parent = task;

                while (parent) {
                    if (draggedTask.get("id") === parent.get("id")) {
                        dropAllowed = false;
                        break;
                    }
                    parent = that.dataSource.taskParent(parent);
                }
            };
            var defineLimits = function () {
                var height = $(dropTarget).height();
                var offsetTop = kendo.getOffset(dropTarget).top;

                extend(dropTarget, {
                    beforeLimit: offsetTop + height * 0.25,
                    afterLimit: offsetTop + height * 0.75
                });
            };
            var defineAction = function (coordinate) {
                var location = coordinate.location;
                var className = listStyles.dropAdd;
                var command = "add";
                var level = parseInt(dropTarget.attr(kendo.attr("level")), 10);
                var sibling;

                if (location <= dropTarget.beforeLimit) {
                    sibling = dropTarget.prev();
                    className = listStyles.dropTop;
                    command = "insert-before";
                } else if (location >= dropTarget.afterLimit) {
                    sibling = dropTarget.next();
                    className = listStyles.dropBottom;
                    command = "insert-after";
                }

                if (sibling && parseInt(sibling.attr(kendo.attr("level")), 10) === level) {
                    className = listStyles.dropMiddle;
                }

                action.className = className;
                action.command = command;
            };
            var status = function () {
                return that._reorderDraggable
                            .hint
                            .children(DOT + listStyles.dragStatus)
                            .removeClass(listStyles.dropPositions);
            };

            if (!this.options.editable) {
                return;
            }

            this._reorderDraggable = this.content
                .kendoDraggable({
                    distance: 10,
                    holdToDrag: mobileOS,
                    group: "listGroup",
                    filter: "tr[data-uid]",
                    ignore: DOT + listStyles.input,
                    hint: function (target) {
                        return $('<div class="' + listStyles.header + " " + listStyles.dragClue + '"/>')
                                .css({
                                    width: 300,
                                    paddingLeft: target.css("paddingLeft"),
                                    paddingRight: target.css("paddingRight"),
                                    lineHeight: target.height() + "px",
                                    paddingTop: target.css("paddingTop"),
                                    paddingBottom: target.css("paddingBottom")
                                })
                                .append('<span class="' + listStyles.icon + " " + listStyles.dragStatus + '" /><span class="' + listStyles.dragClueText + '"/>');
                    },
                    cursorOffset: { top: -20, left: 0 },
                    container: this.content,
                    "dragstart": function (e) {
                        if (that.editable) {
                            e.preventDefault();
                        }
                        draggedTask = that._modelFromElement(e.currentTarget);
                        this.hint.children(DOT + listStyles.dragClueText)
                            .text(draggedTask.get("title"));
                    },
                    "drag": function (e) {
                        if (dropAllowed) {
                            defineAction(e.y);
                            status().addClass(action.className);
                        }
                    },
                    "dragend": function (e) {
                        clear();
                    },
                    "dragcancel": function (e) {
                        clear();
                    }
                }).data("kendoDraggable");

            this._tableDropArea = this.content
                .kendoDropTargetArea({
                    distance: 0,
                    group: "listGroup",
                    filter: "tr[data-uid]",
                    "dragenter": function (e) {
                        dropTarget = e.dropTarget;
                        allowDrop(that._modelFromElement(dropTarget));
                        defineLimits();
                        status().toggleClass(listStyles.dropDenied, !dropAllowed);
                    },
                    "dragleave": function (e) {
                        dropAllowed = true;
                        status();
                    },
                    "drop": function (e) {
                        var target = that._modelFromElement(dropTarget);
                        var orderId = target.orderId;
                        var taskInfo = {
                            parentId: target.parentId
                        };

                        if (dropAllowed) {
                            switch (action.command) {
                                case "add":
                                    taskInfo.parentId = target.id;
                                    break;
                                case "insert-before":
                                    if (target.parentId === draggedTask.parentId &&
                                        target.orderId > draggedTask.orderId) {
                                        taskInfo.orderId = orderId - 1;
                                    } else {
                                        taskInfo.orderId = orderId;
                                    }
                                    break;
                                case "insert-after":
                                    if (target.parentId === draggedTask.parentId &&
                                        target.orderId > draggedTask.orderId) {
                                        taskInfo.orderId = orderId;
                                    } else {
                                        taskInfo.orderId = orderId + 1;
                                    }
                                    break;
                            }
                            that.trigger("update", {
                                task: draggedTask,
                                updateInfo: taskInfo
                            });
                        }
                    }
                }).data("kendoDropTargetArea");

            this._contentDropArea = this.element
               .kendoDropTargetArea({
                   distance: 0,
                   group: "listGroup",
                   filter: DOT + listStyles.gridContent,
                   "drop": function (e) {
                       var target = that._modelFromElement(that.content.find(selector));
                       var orderId = target.orderId;
                       var taskInfo = {
                           parentId: null,
                           orderId: draggedTask.parentId !== null ?
                                        orderId + 1 : orderId
                       };

                       that.trigger("update", {
                           task: draggedTask,
                           updateInfo: taskInfo
                       });
                   }
               }).data("kendoDropTargetArea");
        },

        _modelFromElement: function (element) {
            var row = element.closest("tr");
            var model = this.dataSource.getByUid(row.attr(kendo.attr("uid")));

            return model;
        },

        _columnFromElement: function (element) {
            var td = element.closest("td");
            var tr = td.parent();
            var idx = tr.children().index(td);

            return this.columns[idx];
        }
    });

    extend(true, ui.GanttList, { styles: listStyles });

})(window.kendo.jQuery);


;

(function ($) {

    var Widget = kendo.ui.Widget;
    var kendoDomElement = kendo.dom.element;
    var kendoTextElement = kendo.dom.text;
    var isPlainObject = $.isPlainObject;
    var extend = $.extend;
    var browser = kendo.support.browser;
    var keys = kendo.keys;
    var Query = kendo.data.Query;
    var NS = ".kendoGanttTimeline";
    var CLICK = "click";
    var DBLCLICK = "dblclick";
    var KEYDOWN = "keydown";
    var DOT = ".";
    var TIME_HEADER_TEMPLATE = kendo.template("#=kendo.toString(start, 't')#");
    var DAY_HEADER_TEMPLATE = kendo.template("#=kendo.toString(start, 'ddd M/dd')#");
    var WEEK_HEADER_TEMPLATE = kendo.template("#=kendo.toString(start, 'ddd M/dd')# - #=kendo.toString(kendo.date.addDays(end, -1), 'ddd M/dd')#");
    var MONTH_HEADER_TEMPLATE = kendo.template("#=kendo.toString(start, 'MMM')#");
    var YEAR_HEADER_TEMPLATE = kendo.template("#=kendo.toString(start, 'yyyy')#");
    var RESIZE_HINT = kendo.template('<div class="#=styles.marquee#">' +
                           '<div class="#=styles.marqueeColor#"></div>' +
                       '</div>');
    var RESIZE_TOOLTIP_TEMPLATE = kendo.template('<div style="z-index: 100002;" class="#=styles.tooltipWrapper#">' +
                                   '<div class="#=styles.tooltipContent#">' +
                                        '<div>Start: #=kendo.toString(start, "ddd M/dd HH:mm")#</div>' +
                                        '<div>End: #=kendo.toString(end, "ddd M/dd HH:mm")#</div>' +
                                   '</div>' +
                              '</div>');
    var PERCENT_RESIZE_TOOLTIP_TEMPLATE = kendo.template('<div style="z-index: 100002;" class="#=styles.tooltipWrapper#" >' +
                                   '<div class="#=styles.tooltipContent#">#=text#%</div>' +
                                   '<div class="#=styles.tooltipCallout#" style="left:13px;"></div>' +
                              '</div>');

    var defaultViews = {
        day: {
            type: "kendo.ui.GanttDayView"
        },
        week: {
            type: "kendo.ui.GanttWeekView"
        },
        month: {
            type: "kendo.ui.GanttMonthView"
        },
        year: {
            type: "kendo.ui.GanttYearView"
        }
    };

    function trimOptions(options) {
        delete options.name;
        delete options.prefix;
        delete options.views;

        return options;
    }

    function getWorkDays(options) {
        var workDays = [];
        var dayIndex = options.workWeekStart;

        workDays.push(dayIndex);

        while (options.workWeekEnd != dayIndex) {
            if (dayIndex > 6) {
                dayIndex -= 7;
            } else {
                dayIndex++;
            }
            workDays.push(dayIndex);
        }
        return workDays;
    }

    function blurActiveElement() {
        var activeElement = kendo._activeElement();

        if (activeElement.nodeName.toLowerCase() !== "body") {
            $(activeElement).blur();
        }
    }

    var viewStyles = {
        alt: "k-alt",
        nonWorking: "k-nonwork-hour",
        header: "k-header",
        gridHeader: "k-grid-header",
        gridHeaderWrap: "k-grid-header-wrap",
        gridContent: "k-grid-content",
        rowsTable: "k-gantt-rows",
        columnsTable: "k-gantt-columns",
        tasksTable: "k-gantt-tasks",
        resource: "k-resource",
        resourceAlt: "k-resource k-alt",
        task: "k-task",
        taskSingle: "k-task-single",
        taskMilestone: "k-task-milestone",
        taskSummary: "k-task-summary",
        taskWrap: "k-task-wrap",
        taskMilestoneWrap: "k-milestone-wrap",
        resourcesWrap: "k-resources-wrap",
        taskDot: "k-task-dot",
        taskDotStart: "k-task-start",
        taskDotEnd: "k-task-end",
        taskDragHandle: "k-task-draghandle",
        taskContent: "k-task-content",
        taskTemplate: "k-task-template",
        taskActions: "k-task-actions",
        taskDelete: "k-task-delete",
        taskComplete: "k-task-complete",
        link: "k-link",
        icon: "k-icon",
        iconDelete: "k-si-close",
        taskResizeHandle: "k-resize-handle",
        taskResizeHandleWest: "k-resize-w",
        taskResizeHandleEast: "k-resize-e",
        taskSummaryProgress: "k-task-summary-progress",
        taskSummaryComplete: "k-task-summary-complete",
        line: "k-line",
        lineHorizontal: "k-line-h",
        lineVertical: "k-line-v",
        arrowWest: "k-arrow-w",
        arrowEast: "k-arrow-e",
        dragHint: "k-drag-hint",
        dependencyHint: "k-dependency-hint",
        tooltipWrapper: "k-widget k-tooltip k-popup k-group k-reset",
        tooltipContent: "k-tooltip-content",
        tooltipCallout: "k-callout k-callout-s",
        callout: "k-callout",
        marquee: "k-marquee k-gantt-marquee",
        marqueeColor: "k-marquee-color"
    };

    var GanttView = kendo.ui.GanttView = Widget.extend({
        init: function (element, options) {
            Widget.fn.init.call(this, element, options);

            this.title = this.options.title || this.options.name;

            this.header = this.element.find(DOT + GanttView.styles.gridHeader);

            this.content = this.element.find(DOT + GanttView.styles.gridContent);

            this.contentWidth = this.content.width();

            this._workDays = getWorkDays(this.options);

            this._headerTree = options.headerTree;

            this._taskTree = options.taskTree;

            this._dependencyTree = options.dependencyTree;

            this._taskCoordinates = {};
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            this.headerRow = null;
            this.header = null;
            this.content = null;

            this._dragHint = null;
            this._resizeHint = null;
            this._resizeTooltip = null;
            this._percentCompleteResizeTooltip = null;

            this._headerTree = null;
            this._taskTree = null;
            this._dependencyTree = null;
        },

        options: {
            showWorkHours: false,
            showWorkDays: false,
            workDayStart: new Date(1980, 1, 1, 8, 0, 0),
            workDayEnd: new Date(1980, 1, 1, 17, 0, 0),
            workWeekStart: 1,
            workWeekEnd: 5,
            hourSpan: 1,
            slotSize: 100
        },

        renderLayout: function () {
            this._slots = this._createSlots();

            this._tableWidth = this._calculateTableWidth();

            this.createLayout(this._layout());

            this._slotDimensions();

            this._adjustHeight();
        },

        _adjustHeight: function () {
            this.content.height(this.element.height() - this.header.outerHeight());
        },

        createLayout: function (rows) {
            var headers = this._headers(rows);
            var colgroup = this._colgroup();
            var tree = this._headerTree;
            var header = kendoDomElement("thead", null, headers);
            var table = kendoDomElement("table", { style: { width: this._tableWidth + "px"} }, [colgroup, header]);

            tree.render([table]);

            this.headerRow = this.header.find("table:first tr").last();
        },

        _slotDimensions: function () {
            var headers = this.headerRow[0].children;
            var slots = this._timeSlots();
            var slot;
            var header;

            for (var i = 0, length = headers.length; i < length; i++) {
                header = headers[i];
                slot = slots[i];

                slot.offsetLeft = header.offsetLeft;
                slot.offsetWidth = header.offsetWidth;
            }
        },

        render: function (tasks) {
            var taskCount = tasks.length;
            var styles = GanttView.styles;

            var contentTable;
            var rowsTable = this._rowsTable(taskCount);
            var columnsTable = this._columnsTable(taskCount);
            var tasksTable = this._tasksTable(tasks);

            this._taskTree.render([rowsTable, columnsTable, tasksTable]);

            contentTable = this.content.find(DOT + styles.rowsTable);

            this._contentHeight = contentTable.height();
            this._rowHeight = contentTable.find("tr").height();

            this.content.find(DOT + styles.columnsTable).height(this._contentHeight);
        },

        _rowsTable: function (rowCount) {
            var rows = [];
            var row;
            var styles = GanttView.styles;
            var attributes = [null, { className: styles.alt}];

            for (var i = 0; i < rowCount; i++) {
                row = kendoDomElement("tr", attributes[i % 2], [
                    kendoDomElement("td", null, [
                        kendoTextElement("\u00a0")
                    ])
                ]);

                rows.push(row);
            }

            return this._createTable(1, rows, { className: styles.rowsTable });
        },

        _columnsTable: function (rowCount) {
            var cells = [];
            var row;
            var styles = GanttView.styles;
            var slots = this._timeSlots();
            var slotsCount = slots.length;
            var slot;
            var slotSpan;
            var totalSpan = 0;
            var attributes;

            for (var i = 0; i < slotsCount; i++) {
                slot = slots[i];

                attributes = {};

                slotSpan = slot.span;

                totalSpan += slotSpan;

                if (slotSpan !== 1) {
                    attributes.colspan = slotSpan;
                }

                if (slot.isNonWorking) {
                    attributes.className = styles.nonWorking;
                }

                cells.push(kendoDomElement("td", attributes, [
                    kendoTextElement("\u00a0")
                ]));
            }

            row = kendoDomElement("tr", null, cells);

            return this._createTable(totalSpan, [row], { className: styles.columnsTable });
        },

        _tasksTable: function (tasks) {
            var rows = [];
            var row;
            var cell;
            var position;
            var task;
            var styles = GanttView.styles;
            var coordinates = this._taskCoordinates = {};
            var size = this._calculateMilestoneWidth();
            var milestoneWidth = Math.round(size.width);
            var resourcesField = this.options.resourcesField;
            var className = [styles.resource, styles.resourceAlt];
            var resourcesPosition;
            var resourcesMargin = this._calculateResourcesMargin();

            var addCoordinates = function (rowIndex) {
                var taskLeft;
                var taskRight;

                taskLeft = position.left;
                taskRight = taskLeft + position.width;

                if (task.isMilestone()) {
                    taskLeft -= milestoneWidth / 2;
                    taskRight = taskLeft + milestoneWidth;
                }

                coordinates[task.id] = {
                    start: taskLeft,
                    end: taskRight,
                    rowIndex: rowIndex
                };
            };

            for (var i = 0, l = tasks.length; i < l; i++) {
                task = tasks[i];

                position = this._taskPosition(task);

                row = kendoDomElement("tr", null);

                cell = kendoDomElement("td", null, [this._renderTask(tasks[i], position)]);

                if (task[resourcesField] && task[resourcesField].length) {
                    resourcesPosition = Math.max((position.width || size.clientWidth), 0) + position.left;

                    cell.children.push(kendoDomElement("div",
                        {
                            className: styles.resourcesWrap,
                            style: { left: resourcesPosition + "px", width: (this._tableWidth - (resourcesPosition + resourcesMargin)) + "px" }
                        },
                        this._renderResources(task[resourcesField], className[i % 2])));
                }

                row.children.push(cell);

                rows.push(row);

                addCoordinates(i);
            }

            return this._createTable(1, rows, { className: GanttView.styles.tasksTable });
        },

        _createTable: function (colspan, rows, styles) {
            var cols = [];
            var colgroup;
            var tbody;

            for (var i = 0; i < colspan; i++) {
                cols.push(kendoDomElement("col"));
            }

            colgroup = kendoDomElement("colgroup", null, cols);

            tbody = kendoDomElement("tbody", null, rows);

            if (!styles.style) {
                styles.style = {};
            }

            styles.style.width = this._tableWidth + "px";

            return kendoDomElement("table", styles, [colgroup, tbody]);
        },

        _calculateTableWidth: function () {
            var slots = this._timeSlots();
            var maxSpan = 0;
            var totalSpan = 0;
            var currentSpan;
            var tableWidth;

            for (var i = 0, length = slots.length; i < length; i++) {
                currentSpan = slots[i].span;

                totalSpan += currentSpan;

                if (currentSpan > maxSpan) {
                    maxSpan = currentSpan;
                }
            }

            tableWidth = Math.round((totalSpan * this.options.slotSize) / maxSpan);

            return tableWidth;
        },

        _calculateMilestoneWidth: function () {
            var size;
            var className = GanttView.styles.task + " " + GanttView.styles.taskMilestone;
            var milestone = $("<div class='" + className + "' style='visibility: hidden; position: absolute'>");

            this.content.append(milestone);

            size = {
                "width": milestone[0].getBoundingClientRect().width,
                "clientWidth": milestone[0].clientWidth
            };

            milestone.remove();

            return size;
        },

        _calculateResourcesMargin: function () {
            var margin;
            var wrapper = $("<div class='" + GanttView.styles.resourcesWrap + "' style='visibility: hidden; position: absolute'>");

            this.content.append(wrapper);

            margin = parseInt(wrapper.css("margin-left"), 10);

            wrapper.remove();

            return margin;
        },

        _renderTask: function (task, position) {
            var taskWrapper;
            var taskElement;
            var editable = this.options.editable;
            var progressHandleLeft;
            var taskLeft = position.left;
            var styles = GanttView.styles;
            var wrapClassName = styles.taskWrap;

            if (task.summary) {
                taskElement = this._renderSummary(task, position);
            } else if (task.isMilestone()) {
                taskElement = this._renderMilestone(task, position);
                wrapClassName += " " + styles.taskMilestoneWrap;
            } else {
                taskElement = this._renderSingleTask(task, position);
            }

            taskWrapper = kendoDomElement("div", { className: wrapClassName, style: { left: taskLeft + "px"} }, [
                taskElement
            ]);

            if (editable) {
                taskWrapper.children.push(kendoDomElement("div", { className: styles.taskDot + " " + styles.taskDotStart }));
                taskWrapper.children.push(kendoDomElement("div", { className: styles.taskDot + " " + styles.taskDotEnd }));
            }

            if (!task.summary && !task.isMilestone() && editable) {
                progressHandleLeft = Math.round(position.width * task.percentComplete);

                taskWrapper.children.push(kendoDomElement("div", { className: styles.taskDragHandle, style: { left: progressHandleLeft + "px"} }));
            }

            return taskWrapper;
        },

        _renderSingleTask: function (task, position) {
            var styles = GanttView.styles;
            var progressWidth = Math.round(position.width * task.percentComplete);

            var content = kendoDomElement("div", { className: styles.taskContent }, [
                kendoDomElement("div", { className: styles.taskTemplate }, [
                    kendoTextElement(task.title)
                ])
            ]);

            if (this.options.editable) {
                content.children.push(kendoDomElement("span", { className: styles.taskActions }, [
                    kendoDomElement("a", { className: styles.link + " " + styles.taskDelete, href: "#" }, [
                        kendoDomElement("span", { className: styles.icon + " " + styles.iconDelete })
                    ])
                ]));

                content.children.push(kendoDomElement("span", { className: styles.taskResizeHandle + " " + styles.taskResizeHandleWest }));

                content.children.push(kendoDomElement("span", { className: styles.taskResizeHandle + " " + styles.taskResizeHandleEast }));
            }

            var element = kendoDomElement("div", { className: styles.task + " " + styles.taskSingle, "data-uid": task.uid, style: { width: Math.max((position.width - 2), 0) + "px"} }, [
                kendoDomElement("div", { className: styles.taskComplete, style: { width: progressWidth + "px"} }),
                content
            ]);

            return element;
        },

        _renderMilestone: function (task, position) {
            var styles = GanttView.styles;
            var element = kendoDomElement("div", { className: styles.task + " " + styles.taskMilestone, "data-uid": task.uid });

            return element;
        },

        _renderSummary: function (task, position) {
            var styles = GanttView.styles;
            var progressWidth = Math.round(position.width * task.percentComplete);

            var element = kendoDomElement("div", { className: styles.task + " " + styles.taskSummary, "data-uid": task.uid, style: { width: position.width + "px"} }, [
                kendoDomElement("div", { className: styles.taskSummaryProgress, style: { width: progressWidth + "px"} }, [
                    kendoDomElement("div", { className: styles.taskSummaryComplete, style: { width: position.width + "px"} })
                ])
            ]);

            return element;
        },

        _renderResources: function (resources, className) {
            var children = [];
            var resource;

            for (var i = 0, length = resources.length; i < length; i++) {
                resource = resources[i];
                children.push(kendoDomElement("span", {
                    className: className,
                    style: {
                        "color": resource.get("color")
                    }
                }, [kendoTextElement(resource.get("name"))]));
            }

            return children;
        },

        _taskPosition: function (task) {
            var round = Math.round;
            var startLeft = round(this._offset(task.start));
            var endLeft = round(this._offset(task.end));

            return { left: startLeft, width: endLeft - startLeft };
        },

        _offset: function (date) {
            var slots = this._timeSlots();
            var slot;
            var startOffset;
            var slotDuration;
            var slotOffset;
            var startIndex = this._slotIndex("start", date);

            slot = slots[startIndex];

            if (slot.end < date) {
                return slot.offsetLeft + slot.offsetWidth;
            }

            if (slot.start > date) {
                return slot.offsetLeft;
            }

            startOffset = date - slot.start;
            slotDuration = slot.end - slot.start;
            slotOffset = (startOffset / slotDuration) * slot.offsetWidth;

            return slot.offsetLeft + slotOffset;
        },

        _slotIndex: function (field, value) {
            var slots = this._timeSlots();
            var startIdx = 0;
            var endIdx = slots.length - 1;
            var middle;

            do {
                middle = Math.ceil((endIdx + startIdx) / 2);

                if (slots[middle][field] < value) {
                    startIdx = middle;
                } else {
                    if (middle === endIdx) {
                        middle--;
                    }

                    endIdx = middle;
                }
            } while (startIdx !== endIdx);

            return startIdx;
        },

        _timeByPosition: function (x, snap, snapToEnd) {
            var slot = this._slotByPosition(x);

            if (snap) {
                return snapToEnd ? slot.end : slot.start;
            }

            var offsetLeft = x - (this.content.offset().left - this.content.scrollLeft());
            var duration = slot.end - slot.start;
            var slotOffset = duration * ((offsetLeft - slot.offsetLeft) / slot.offsetWidth);

            return new Date(slot.start.getTime() + slotOffset);
        },

        _slotByPosition: function (x) {
            var offsetLeft = x - (this.content.offset().left - this.content.scrollLeft());
            var slotIndex = this._slotIndex("offsetLeft", offsetLeft);

            return this._timeSlots()[slotIndex];
        },

        _renderDependencies: function (dependencies) {
            var elements = [];
            var tree = this._dependencyTree;

            for (var i = 0, l = dependencies.length; i < l; i++) {
                elements.push.apply(elements, this._renderDependency(dependencies[i]));

            }

            tree.render(elements);
        },

        _renderDependency: function (dependency) {
            var predecessor = this._taskCoordinates[dependency.predecessorId];
            var successor = this._taskCoordinates[dependency.successorId];
            var elements;
            var method;

            if (!predecessor || !successor) {
                return [];
            }

            method = "_render" + ["FF", "FS", "SF", "SS"][dependency.type];
            elements = this[method](predecessor, successor);

            for (var i = 0, length = elements.length; i < length; i++) {
                elements[i].attr["data-uid"] = dependency.uid;
            }

            return elements;
        },

        _renderFF: function (from, to) {
            var lines = this._dependencyFF(from, to, false);

            lines[lines.length - 1].children[0] = this._arrow(true);

            return lines;
        },

        _renderSS: function (from, to) {
            var lines = this._dependencyFF(to, from, true);

            lines[0].children[0] = this._arrow(false);

            return lines.reverse();
        },

        _renderFS: function (from, to) {
            var lines = this._dependencyFS(from, to, false);

            lines[lines.length - 1].children[0] = this._arrow(false);

            return lines;
        },

        _renderSF: function (from, to) {
            var lines = this._dependencyFS(to, from, true);

            lines[0].children[0] = this._arrow(true);

            return lines.reverse();
        },

        _dependencyFF: function (from, to, reverse) {
            var that = this;
            var lines = [];
            var left = 0;
            var top = 0;
            var width = 0;
            var height = 0;
            var dir = reverse ? "start" : "end";
            var delta;
            var overlap = 2;
            var arrowOverlap = 1;
            var rowHeight = this._rowHeight;
            var minLineWidth = 10;
            var fromTop = from.rowIndex * rowHeight + Math.floor(rowHeight / 2) - 1;
            var toTop = to.rowIndex * rowHeight + Math.floor(rowHeight / 2) - 1;
            var styles = GanttView.styles;

            var addHorizontal = function () {
                lines.push(that._line(styles.line + " " + styles.lineHorizontal, { left: left + "px", top: top + "px", width: width + "px" }));
            };
            var addVertical = function () {
                lines.push(that._line(styles.line + " " + styles.lineVertical, { left: left + "px", top: top + "px", height: height + "px" }));
            };

            left = from[dir];
            top = fromTop;
            width = minLineWidth;

            delta = to[dir] - from[dir];

            if ((delta) > 0 !== reverse) {
                width = Math.abs(delta) + minLineWidth;
            }

            if (reverse) {
                left -= width;
                width -= arrowOverlap;
                addHorizontal();
            } else {
                addHorizontal();
                left += width - overlap;
            }

            if (toTop < top) {
                height = top - toTop;
                height += overlap;
                top = toTop;
                addVertical();
            } else {
                height = toTop - top;
                height += overlap;
                addVertical();
                top += (height - overlap);
            }

            width = Math.abs(left - to[dir]);

            if (!reverse) {
                width -= arrowOverlap;
                left -= width;
            }

            addHorizontal();

            return lines;
        },

        _dependencyFS: function (from, to, reverse) {
            var that = this;
            var lines = [];
            var left = 0;
            var top = 0;
            var width = 0;
            var height = 0;
            var rowHeight = this._rowHeight;
            var minLineHeight = Math.floor(rowHeight / 2);
            var minLineWidth = 10;
            var minDistance = 2 * minLineWidth;
            var delta = to.start - from.end;
            var overlap = 2;
            var arrowOverlap = 1;
            var fromTop = from.rowIndex * rowHeight + Math.floor(rowHeight / 2) - 1;
            var toTop = to.rowIndex * rowHeight + Math.floor(rowHeight / 2) - 1;
            var styles = GanttView.styles;

            var addHorizontal = function () {
                lines.push(that._line(styles.line + " " + styles.lineHorizontal, { left: left + "px", top: top + "px", width: width + "px" }));
            };
            var addVertical = function () {
                lines.push(that._line(styles.line + " " + styles.lineVertical, { left: left + "px", top: top + "px", height: height + "px" }));
            };

            left = from.end;
            top = fromTop;
            width = minLineWidth;

            if (reverse) {
                left += arrowOverlap;

                if (delta > minDistance) {
                    width = delta - (minLineWidth - overlap);
                }

                width -= arrowOverlap;
            }

            addHorizontal();
            left += width - overlap;

            if ((delta) <= minDistance) {
                height = reverse ? Math.abs(toTop - fromTop) - minLineHeight : minLineHeight;

                if (toTop < fromTop) {
                    top -= height;

                    height += overlap;

                    addVertical();
                } else {
                    addVertical();
                    top += height;
                }

                width = (from.end - to.start) + minDistance;

                if (width < minLineWidth) {
                    width = minLineWidth;
                }

                left -= width - overlap;

                addHorizontal();
            }

            if (toTop < fromTop) {
                height = top - toTop;
                top = toTop;

                height += overlap;

                addVertical();
            } else {
                height = toTop - top;
                addVertical();
                top += height;
            }

            width = to.start - left;

            if (!reverse) {
                width -= arrowOverlap;
            }

            addHorizontal();

            return lines;
        },

        _line: function (className, styles) {
            return kendoDomElement("div", { className: className, style: styles });
        },

        _arrow: function (direction) {
            return kendoDomElement("span", { className: direction ? GanttView.styles.arrowWest : GanttView.styles.arrowEast });
        },

        _colgroup: function () {
            var slots = this._timeSlots();
            var count = slots.length;
            var cols = [];

            for (var i = 0; i < count; i++) {
                for (var j = 0, length = slots[i].span; j < length; j++) {
                    cols.push(kendoDomElement("col"));
                }
            }

            return kendoDomElement("colgroup", null, cols);
        },

        _createDragHint: function (element) {
            this._dragHint = element
                .clone()
                .addClass(GanttView.styles.dragHint)
                .css("cursor", "move");

            element
                .parent()
                .append(this._dragHint);
        },

        _updateDragHint: function (start) {
            var left = this._offset(start);

            this._dragHint
                .css({
                    "left": left
                });
        },

        _removeDragHint: function () {
            this._dragHint.remove();
            this._dragHint = null;
        },

        _createResizeHint: function (task) {
            var styles = GanttView.styles;
            var taskTop = this._taskCoordinates[task.id].rowIndex * this._rowHeight;
            var tooltipHeight;
            var tooltipTop;

            this._resizeHint = $(RESIZE_HINT({ styles: styles })).css({
                "top": 0,
                "height": this._contentHeight
            });

            this.content.append(this._resizeHint);

            this._resizeTooltip = $(RESIZE_TOOLTIP_TEMPLATE({ styles: styles, start: task.start, end: task.end }))
                .css({
                    "top": 0,
                    "left": 0
                });

            this.content.append(this._resizeTooltip);

            this._resizeTooltipWidth = this._resizeTooltip.outerWidth();
            tooltipHeight = this._resizeTooltip.outerHeight();

            tooltipTop = taskTop - tooltipHeight;

            if (tooltipTop < 0) {
                tooltipTop = taskTop + this._rowHeight;
            }

            this._resizeTooltipTop = tooltipTop;
        },

        _updateResizeHint: function (start, end, resizeStart) {
            var left = this._offset(start);
            var right = this._offset(end);
            var width = right - left;
            var tooltipLeft = resizeStart ? left : right;
            var tablesWidth = this._tableWidth - 17;
            var tooltipWidth = this._resizeTooltipWidth;

            this._resizeHint
                .css({
                    "left": left,
                    "width": width
                });

            if (this._resizeTooltip) {
                this._resizeTooltip.remove();
            }

            tooltipLeft -= Math.round(tooltipWidth / 2);

            if (tooltipLeft < 0) {
                tooltipLeft = 0;
            } else if (tooltipLeft + tooltipWidth > tablesWidth) {
                tooltipLeft = tablesWidth - tooltipWidth;
            }

            this._resizeTooltip = $(RESIZE_TOOLTIP_TEMPLATE({ styles: GanttView.styles, start: start, end: end }))
                .css({
                    "top": this._resizeTooltipTop,
                    "left": tooltipLeft,
                    "min-width": tooltipWidth
                });

            this.content.append(this._resizeTooltip);
        },

        _removeResizeHint: function () {
            this._resizeHint.remove();
            this._resizeHint = null;

            this._resizeTooltip.remove();
            this._resizeTooltip = null;
        },

        _updatePercentCompleteTooltip: function (top, left, text) {
            this._removePercentCompleteTooltip();

            var tooltip = this._percentCompleteResizeTooltip = $(PERCENT_RESIZE_TOOLTIP_TEMPLATE({ styles: GanttView.styles, text: text }))
                .appendTo(this.element);

            var tooltipMiddle = Math.round(tooltip.outerWidth() / 2);
            var arrow = tooltip.find(DOT + GanttView.styles.callout);
            var arrowHeight = Math.round(arrow.outerWidth() / 2);

            tooltip.css({
                "top": top - (tooltip.outerHeight() + arrowHeight),
                "left": left - tooltipMiddle
            });

            arrow.css("left", tooltipMiddle - arrowHeight);
        },

        _removePercentCompleteTooltip: function () {
            if (this._percentCompleteResizeTooltip) {
                this._percentCompleteResizeTooltip.remove();
            }

            this._percentCompleteResizeTooltip = null;
        },

        _updateDependencyDragHint: function (from, to, useVML) {
            this._removeDependencyDragHint();

            if (useVML) {
                this._creteVmlDependencyDragHint(from, to);
            } else {
                this._creteDependencyDragHint(from, to);
            }
        },

        _creteDependencyDragHint: function (from, to) {
            var styles = GanttView.styles;

            var deltaX = to.x - from.x;
            var deltaY = to.y - from.y;

            var width = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
            var angle = Math.atan(deltaY / deltaX);

            if (deltaX < 0) {
                angle += Math.PI;
            }

            $("<div class='" + styles.line + " " + styles.lineHorizontal + " " + styles.dependencyHint + "'></div>")
                .css({
                    "top": from.y,
                    "left": from.x,
                    "width": width,
                    "transform-origin": "0% 0",
                    "-ms-transform-origin": "0% 0",
                    "-webkit-transform-origin": "0% 0",
                    "transform": "rotate(" + angle + "rad)",
                    "-ms-transform": "rotate(" + angle + "rad)",
                    "-webkit-transform": "rotate(" + angle + "rad)"
                })
                .appendTo(this.content);
        },

        _creteVmlDependencyDragHint: function (from, to) {
            var hint = $("<kvml:line class='" + GanttView.styles.dependencyHint + "' style='position:absolute; top: 0px;' strokecolor='black' strokeweight='2px' from='" +
                from.x + "px," + from.y + "px' to='" + to.x + "px," + to.y + "px'" + "></kvml:line>")
                .appendTo(this.content);

            // IE8 Bug
            hint[0].outerHTML = hint[0].outerHTML;
        },

        _removeDependencyDragHint: function () {
            this.content.find(DOT + GanttView.styles.dependencyHint).remove();
        },

        _scrollTo: function (element) {
            var elementLeft = element.offset().left;
            var elementWidth = element.width();
            var elementRight = elementLeft + elementWidth;

            var row = element.closest("tr");
            var rowTop = row.offset().top;
            var rowHeight = row.height();
            var rowBottom = rowTop + rowHeight;

            var content = this.content;
            var contentOffset = content.offset();
            var contentTop = contentOffset.top;
            var contentHeight = content.height();
            var contentBottom = contentTop + contentHeight;
            var contentLeft = contentOffset.left;
            var contentWidth = content.width();
            var contentRight = contentLeft + contentWidth;

            var scrollbarWidth = kendo.support.scrollbar();

            if (rowTop < contentTop) {
                content.scrollTop(content.scrollTop() + (rowTop - contentTop));
            } else if (rowBottom > contentBottom) {
                content.scrollTop(content.scrollTop() + (rowBottom + scrollbarWidth - contentBottom));
            }

            if (elementLeft < contentLeft && elementWidth > contentWidth && elementRight < contentRight ||
                elementRight > contentRight && elementWidth < contentWidth) {
                content.scrollLeft(content.scrollLeft() + (elementRight + scrollbarWidth - contentRight));
            } else if (elementRight > contentRight && elementWidth > contentWidth && elementLeft > contentLeft ||
                elementLeft < contentLeft && elementWidth < contentWidth) {
                content.scrollLeft(content.scrollLeft() + (elementLeft - contentLeft));
            }
        },

        _timeSlots: function () {
            return this._slots[this._slots.length - 1];
        },

        _headers: function (columnLevels) {
            var rows = [];
            var level;
            var headers;
            var column;
            var headerText;
            var styles = GanttView.styles;

            for (var levelIndex = 0, levelCount = columnLevels.length; levelIndex < levelCount; levelIndex++) {
                level = columnLevels[levelIndex];
                headers = [];

                for (var columnIndex = 0, columnCount = level.length; columnIndex < columnCount; columnIndex++) {
                    column = level[columnIndex];

                    headerText = kendoTextElement(column.text);
                    headers.push(kendoDomElement("th", { colspan: column.span, className: styles.header + (column.isNonWorking ? (" " + styles.nonWorking) : "") }, [headerText]));
                }

                rows.push(kendoDomElement("tr", null, headers));
            }

            return rows;
        },

        _hours: function (start, end) {
            var slotEnd;
            var slots = [];
            var options = this.options;
            var workDayStart = options.workDayStart.getHours();
            var workDayEnd = options.workDayEnd.getHours();
            var isWorkHour;
            var hours;
            var hourSpan = options.hourSpan;

            start = new Date(start);
            end = new Date(end);

            while (start < end) {
                slotEnd = new Date(start);
                hours = slotEnd.getHours();

                isWorkHour = hours >= workDayStart && hours < workDayEnd;

                slotEnd.setHours(slotEnd.getHours() + hourSpan);

                if (hours == slotEnd.getHours()) {
                    // Chrome DTS Fix
                    slotEnd.setHours(slotEnd.getHours() + 2 * hourSpan);
                }

                if (!options.showWorkHours || isWorkHour) {
                    slots.push({
                        start: start,
                        end: slotEnd,
                        isNonWorking: !isWorkHour,
                        span: 1
                    });
                }

                start = slotEnd;
            }

            return slots;
        },

        _days: function (start, end) {
            var slotEnd;
            var slots = [];
            var isWorkDay;

            start = new Date(start);
            end = new Date(end);

            while (start < end) {
                slotEnd = kendo.date.nextDay(start);

                isWorkDay = this._isWorkDay(start);

                if (!this.options.showWorkDays || isWorkDay) {
                    slots.push({
                        start: start,
                        end: slotEnd,
                        isNonWorking: !isWorkDay,
                        span: 1
                    });
                }

                start = slotEnd;
            }

            return slots;
        },

        _weeks: function (start, end) {
            var slotEnd;
            var slots = [];
            var firstDay = this.calendarInfo().firstDay;
            var daySlots;
            var span;

            start = new Date(start);
            end = new Date(end);

            while (start < end) {
                slotEnd = kendo.date.dayOfWeek(kendo.date.addDays(start, 1), firstDay, 1);

                if (slotEnd > end) {
                    slotEnd = end;
                }

                daySlots = this._days(start, slotEnd);
                span = daySlots.length;

                if (span > 0) {
                    slots.push({
                        start: daySlots[0].start,
                        end: daySlots[span - 1].end,
                        span: span
                    });
                }

                start = slotEnd;
            }

            return slots;
        },

        _months: function (start, end) {
            var slotEnd;
            var slots = [];
            var daySlots;
            var span;

            start = new Date(start);
            end = new Date(end);

            while (start < end) {
                slotEnd = new Date(start);
                slotEnd.setMonth(slotEnd.getMonth() + 1);

                daySlots = this._days(start, slotEnd);
                span = daySlots.length;

                if (span > 0) {
                    slots.push({
                        start: daySlots[0].start,
                        end: daySlots[span - 1].end,
                        span: span
                    });
                }

                start = slotEnd;
            }

            return slots;
        },

        _years: function (start, end) {
            var slotEnd;
            var slots = [];

            start = new Date(start);
            end = new Date(end);

            while (start < end) {
                slotEnd = new Date(start);
                slotEnd.setFullYear(slotEnd.getFullYear() + 1);

                slots.push({
                    start: start,
                    end: slotEnd,
                    span: 12
                });

                start = slotEnd;
            }

            return slots;
        },

        _slotHeaders: function (slots, template) {
            var columns = [];
            var slot;

            for (var i = 0, l = slots.length; i < l; i++) {
                slot = slots[i];

                columns.push({
                    text: template(slot),
                    isNonWorking: !!slot.isNonWorking,
                    span: slot.span
                });
            }

            return columns;
        },

        _isWorkDay: function (date) {
            var day = date.getDay();
            var workDays = this._workDays;

            for (var i = 0, l = workDays.length; i < l; i++) {
                if (workDays[i] === day) {
                    return true;
                }
            }

            return false;
        },

        calendarInfo: function () {
            return kendo.getCulture().calendars.standard;
        }
    });

    extend(true, GanttView, { styles: viewStyles });

    kendo.ui.GanttDayView = GanttView.extend({
        name: "day",

        options: {
            timeHeaderTemplate: TIME_HEADER_TEMPLATE,
            dayHeaderTemplate: DAY_HEADER_TEMPLATE
        },

        range: function (range) {
            this.start = kendo.date.getDate(range.start);
            this.end = kendo.date.getDate(range.end);

            if (kendo.date.getMilliseconds(range.end) > 0 || this.end.getTime() === this.start.getTime()) {
                this.end = kendo.date.addDays(this.end, 1);
            }
        },

        _createSlots: function () {
            var daySlots;
            var daySlot;
            var hourSlots;
            var hours;
            var slots = [];

            daySlots = this._days(this.start, this.end);
            hourSlots = [];

            for (var i = 0, l = daySlots.length; i < l; i++) {
                daySlot = daySlots[i];
                hours = this._hours(daySlot.start, daySlot.end);

                daySlot.span = hours.length;

                hourSlots.push.apply(hourSlots, hours);
            }

            slots.push(daySlots);
            slots.push(hourSlots);

            return slots;
        },

        _layout: function () {
            var rows = [];
            var options = this.options;

            rows.push(this._slotHeaders(this._slots[0], kendo.template(options.dayHeaderTemplate)));
            rows.push(this._slotHeaders(this._slots[1], kendo.template(options.timeHeaderTemplate)));

            return rows;
        }
    });

    kendo.ui.GanttWeekView = GanttView.extend({
        name: "week",

        options: {
            dayHeaderTemplate: DAY_HEADER_TEMPLATE,
            weekHeaderTemplate: WEEK_HEADER_TEMPLATE
        },

        range: function (range) {
            var calendarInfo = this.calendarInfo();
            var firstDay = calendarInfo.firstDay;

            this.start = kendo.date.getDate(kendo.date.dayOfWeek(range.start, firstDay, -1));
            this.end = kendo.date.getDate(kendo.date.dayOfWeek(range.end, firstDay, 1));
        },

        _createSlots: function () {
            var slots = [];

            slots.push(this._weeks(this.start, this.end));
            slots.push(this._days(this.start, this.end));

            return slots;
        },

        _layout: function () {
            var rows = [];
            var options = this.options;

            rows.push(this._slotHeaders(this._slots[0], kendo.template(options.weekHeaderTemplate)));
            rows.push(this._slotHeaders(this._slots[1], kendo.template(options.dayHeaderTemplate)));

            return rows;
        }
    });

    kendo.ui.GanttMonthView = GanttView.extend({
        name: "month",

        options: {
            weekHeaderTemplate: WEEK_HEADER_TEMPLATE,
            monthHeaderTemplate: MONTH_HEADER_TEMPLATE
        },

        range: function (range) {
            this.start = kendo.date.firstDayOfMonth(range.start);
            this.end = kendo.date.addDays(kendo.date.getDate(kendo.date.lastDayOfMonth(range.end)), 1);
        },

        _createSlots: function () {
            var slots = [];

            slots.push(this._months(this.start, this.end));
            slots.push(this._weeks(this.start, this.end));

            return slots;
        },

        _layout: function () {
            var rows = [];
            var options = this.options;

            rows.push(this._slotHeaders(this._slots[0], kendo.template(options.monthHeaderTemplate)));
            rows.push(this._slotHeaders(this._slots[1], kendo.template(options.weekHeaderTemplate)));

            return rows;
        }
    });

    kendo.ui.GanttYearView = GanttView.extend({
        name: "year",

        options: {
            yearHeaderTemplate: YEAR_HEADER_TEMPLATE,
            monthHeaderTemplate: MONTH_HEADER_TEMPLATE
        },

        range: function (range) {
            this.start = kendo.date.firstDayOfMonth(new Date(range.start.setMonth(0)));
            this.end = kendo.date.firstDayOfMonth(new Date(range.end.setMonth(12))); //set month to first month of next year
        },

        _createSlots: function () {
            var slots = [];
            var monthSlots = this._months(this.start, this.end);

            $(monthSlots).each(function (index, slot) {
                slot.span = 1;
            });

            slots.push(this._years(this.start, this.end));
            slots.push(monthSlots);

            return slots;
        },

        _layout: function () {
            var rows = [];
            var options = this.options;

            rows.push(this._slotHeaders(this._slots[0], kendo.template(options.yearHeaderTemplate)));
            rows.push(this._slotHeaders(this._slots[1], kendo.template(options.monthHeaderTemplate)));

            return rows;
        }
    });

    var timelineStyles = {
        wrapper: "k-timeline k-grid k-widget",
        gridHeader: "k-grid-header",
        gridHeaderWrap: "k-grid-header-wrap",
        gridContent: "k-grid-content",
        gridContentWrap: "k-grid-content",
        tasksWrapper: "k-gantt-tables",
        dependenciesWrapper: "k-gantt-dependencies",
        task: "k-task",
        line: "k-line",
        taskResizeHandle: "k-resize-handle",
        taskResizeHandleWest: "k-resize-w",
        taskDragHandle: "k-task-draghandle",
        taskComplete: "k-task-complete",
        taskDelete: "k-task-delete",
        taskWrapActive: "k-task-wrap-active",
        taskWrap: "k-task-wrap",
        taskDot: "k-task-dot",
        taskDotStart: "k-task-start",
        taskDotEnd: "k-task-end",
        hovered: "k-state-hover",
        selected: "k-state-selected",
        origin: "k-origin"
    };

    var GanttTimeline = kendo.ui.GanttTimeline = Widget.extend({
        init: function (element, options) {

            Widget.fn.init.call(this, element, options);

            if (!this.options.views || !this.options.views.length) {
                this.options.views = ["day", "week", "month"];
            }

            this._wrapper();

            this._domTrees();

            this._views();

            this._selectable();

            this._draggable();

            this._resizable();

            this._percentResizeDraggable();

            this._createDependencyDraggable();

            this._attachEvents();
        },

        options: {
            name: "GanttTimeline",
            messages: {
                views: {
                    day: "Day",
                    week: "Week",
                    month: "Month",
                    year: "Year"
                }
            },
            snap: true,
            selectable: true,
            editable: true
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            this._unbindView(this._selectedView);

            if (this._moveDraggable) {
                this._moveDraggable.destroy();
            }

            if (this._resizeDraggable) {
                this._resizeDraggable.destroy();
            }

            if (this._percentDraggable) {
                this._percentDraggable.destroy();
            }

            if (this._dependencyDraggable) {
                this._dependencyDraggable.destroy();
            }

            this._headerTree = null;
            this._taskTree = null;
            this._dependencyTree = null;

            this.wrapper.off(NS);

            kendo.destroy(this.wrapper);
        },

        _wrapper: function () {
            var styles = GanttTimeline.styles;

            this.wrapper = this.element
                .addClass(styles.wrapper)
                .append("<div class='" + styles.gridHeader + "'><div class='" + styles.gridHeaderWrap + "'></div></div>")
                .append("<div class='" + styles.gridContentWrap + "'><div class='" + styles.tasksWrapper + "'></div><div class='" + styles.dependenciesWrapper + "'></div></div>");
        },

        _domTrees: function () {
            var styles = GanttTimeline.styles;
            var tree = kendo.dom.Tree;
            var wrapper = this.wrapper;

            this._headerTree = new tree(wrapper.find(DOT + styles.gridHeaderWrap)[0]);

            this._taskTree = new tree(wrapper.find(DOT + styles.tasksWrapper)[0]);

            this._dependencyTree = new tree(wrapper.find(DOT + styles.dependenciesWrapper)[0]);
        },

        _views: function () {
            var views = this.options.views;
            var view;
            var isSettings;
            var name;
            var defaultView;
            var selected;

            this.views = {};

            for (var i = 0, l = views.length; i < l; i++) {
                view = views[i];

                isSettings = isPlainObject(view);

                if (isSettings && view.selectable === false) {
                    continue;
                }

                name = isSettings ? ((typeof view.type !== "string") ? view.title : view.type) : view;

                defaultView = defaultViews[name];

                if (defaultView) {
                    if (isSettings) {
                        view.type = defaultView.type;
                    }

                    defaultView.title = this.options.messages.views[name];
                }

                view = extend({ title: name }, defaultView, isSettings ? view : {});

                if (name) {
                    this.views[name] = view;

                    if (!selected || view.selected) {
                        selected = name;
                    }
                }
            }

            if (selected) {
                this._selectedViewName = selected;
            }
        },

        view: function (name) {
            if (name) {
                this._selectView(name);

                this.trigger("navigate", { view: name, action: "changeView" });
            }

            return this._selectedView;
        },

        _selectView: function (name) {
            if (name && this.views[name]) {
                if (this._selectedView) {
                    this._unbindView(this._selectedView);
                }

                this._selectedView = this._initializeView(name);
                this._selectedViewName = name;
            }
        },

        _viewByIndex: function (index) {
            var view;
            var views = this.views;

            for (view in views) {
                if (!index) {
                    return view;
                }

                index--;
            }
        },

        _initializeView: function (name) {
            var view = this.views[name];

            if (view) {
                var type = view.type;

                if (typeof type === "string") {
                    type = kendo.getter(view.type)(window);
                }

                if (type) {
                    view = new type(this.wrapper, trimOptions(extend(true, {
                        headerTree: this._headerTree,
                        taskTree: this._taskTree,
                        dependencyTree: this._dependencyTree
                    }, view, this.options)));
                } else {
                    throw new Error("There is no such view");
                }
            }

            return view;
        },

        _unbindView: function (view) {
            if (view) {
                view.destroy();
            }
        },

        _range: function (tasks) {
            var startOrder = {
                field: "start",
                dir: "asc"
            };
            var endOrder = {
                field: "end",
                dir: "desc"
            };

            if (!tasks || !tasks.length) {
                return { start: new Date(), end: new Date() };
            }

            var start = new Query(tasks).sort(startOrder).toArray()[0].start || new Date();
            var end = new Query(tasks).sort(endOrder).toArray()[0].end || new Date();

            return {
                start: new Date(start),
                end: new Date(end)
            };
        },

        _render: function (tasks) {
            var view = this.view();
            var range = this._range(tasks);

            this._tasks = tasks;

            view.range(range);

            view.renderLayout();

            view.render(tasks);
        },

        _renderDependencies: function (dependencies) {
            this.view()._renderDependencies(dependencies);
        },

        _taskByUid: function (uid) {
            var tasks = this._tasks;
            var length = tasks.length;
            var task;

            for (var i = 0; i < length; i++) {
                task = tasks[i];

                if (task.uid === uid) {
                    return task;
                }
            }
        },

        _draggable: function () {
            var that = this;
            var element;
            var task;
            var currentStart;
            var startOffset;
            var snap = this.options.snap;
            var dragInProgress;
            var styles = GanttTimeline.styles;

            var cleanUp = function () {
                that.view()._removeDragHint();

                if (element) {
                    element.css("opacity", 1);
                }

                element = null;
                task = null;
                dragInProgress = false;
            };

            if (!this.options.editable) {
                return;
            }

            this._moveDraggable = new kendo.ui.Draggable(this.wrapper, {
                distance: 0,
                filter: DOT + styles.task,
                holdToDrag: kendo.support.mobileOS,
                ignore: DOT + styles.taskResizeHandle
            });

            this._moveDraggable
                .bind("dragstart", function (e) {
                    var view = that.view();
                    element = e.currentTarget.parent();
                    task = that._taskByUid(e.currentTarget.attr("data-uid"));

                    if (that.trigger("moveStart", { task: task })) {
                        e.preventDefault();
                        return;
                    }

                    currentStart = task.start;
                    startOffset = view._timeByPosition(e.x.location, snap) - currentStart;

                    view._createDragHint(element);

                    element.css("opacity", 0.5);

                    dragInProgress = true;
                })
                .bind("drag", kendo.throttle(function (e) {
                    if (!dragInProgress) {
                        return;
                    }

                    var view = that.view();
                    var date = new Date(view._timeByPosition(e.x.location, snap) - startOffset);

                    if (!that.trigger("move", { task: task, start: date })) {
                        currentStart = date;

                        view._updateDragHint(currentStart);
                    }
                }, 15))
                .bind("dragend", function (e) {
                    that.trigger("moveEnd", { task: task, start: currentStart });

                    cleanUp();
                })
                .bind("dragcancel", function (e) {
                    cleanUp();
                })
                .userEvents.bind("select", function (e) {
                    blurActiveElement();
                });
        },

        _resizable: function () {
            var that = this;
            var element;
            var task;
            var currentStart;
            var currentEnd;
            var resizeStart;
            var snap = this.options.snap;
            var dragInProgress;
            var styles = GanttTimeline.styles;

            var cleanUp = function () {
                that.view()._removeResizeHint();
                element = null;
                task = null;
                dragInProgress = false;
            };

            if (!this.options.editable) {
                return;
            }

            this._resizeDraggable = new kendo.ui.Draggable(this.wrapper, {
                distance: 0,
                filter: DOT + styles.taskResizeHandle,
                holdToDrag: false
            });

            this._resizeDraggable
                .bind("dragstart", function (e) {
                    resizeStart = e.currentTarget.hasClass(styles.taskResizeHandleWest);

                    element = e.currentTarget.closest(DOT + styles.task);

                    task = that._taskByUid(element.attr("data-uid"));

                    if (that.trigger("resizeStart", { task: task })) {
                        e.preventDefault();
                        return;
                    }

                    currentStart = task.start;
                    currentEnd = task.end;

                    that.view()._createResizeHint(task);

                    dragInProgress = true;
                })
                .bind("drag", kendo.throttle(function (e) {
                    if (!dragInProgress) {
                        return;
                    }

                    var view = that.view();
                    var date = view._timeByPosition(e.x.location, snap, !resizeStart);

                    if (resizeStart) {
                        if (date < currentEnd) {
                            currentStart = date;
                        } else {
                            currentStart = currentEnd;
                        }
                    } else {
                        if (date > currentStart) {
                            currentEnd = date;
                        } else {
                            currentEnd = currentStart;
                        }
                    }

                    if (!that.trigger("resize", { task: task, start: currentStart, end: currentEnd })) {
                        view._updateResizeHint(currentStart, currentEnd, resizeStart);
                    }
                }, 15))
                .bind("dragend", function (e) {
                    var date = resizeStart ? currentStart : currentEnd;

                    that.trigger("resizeEnd", { task: task, resizeStart: resizeStart, start: currentStart, end: currentEnd });

                    cleanUp();
                })
                .bind("dragcancel", function (e) {
                    cleanUp();
                })
                .userEvents.bind("select", function (e) {
                    blurActiveElement();
                });
        },

        _percentResizeDraggable: function () {
            var that = this;
            var task;
            var taskElement;
            var taskElementOffset;
            var timelineOffset;
            var originalPercentWidth;
            var maxPercentWidth;
            var currentPercentComplete;
            var tooltipTop;
            var tooltipLeft;
            var dragInProgress;
            var styles = GanttTimeline.styles;

            var cleanUp = function () {
                that.view()._removePercentCompleteTooltip();
                taskElement = null;
                task = null;
                dragInProgress = false;
            };

            var updateElement = function (width) {
                taskElement
                    .find(DOT + styles.taskComplete)
                    .width(width)
                    .end()
                    .siblings(DOT + styles.taskDragHandle)
                    .css("left", width);
            };

            if (!this.options.editable) {
                return;
            }

            this._percentDraggable = new kendo.ui.Draggable(this.wrapper, {
                distance: 0,
                filter: DOT + styles.taskDragHandle,
                holdToDrag: false
            });

            this._percentDraggable
                .bind("dragstart", function (e) {
                    taskElement = e.currentTarget.siblings(DOT + styles.task);

                    task = that._taskByUid(taskElement.attr("data-uid"));

                    currentPercentComplete = task.percentComplete;

                    taskElementOffset = taskElement.offset();
                    timelineOffset = this.element.offset();

                    originalPercentWidth = taskElement.find(DOT + styles.taskComplete).width();
                    maxPercentWidth = taskElement.outerWidth();

                    dragInProgress = true;
                })
                .bind("drag", kendo.throttle(function (e) {
                    if (!dragInProgress) {
                        return;
                    }

                    var currentWidth = Math.max(0, Math.min(maxPercentWidth, originalPercentWidth + e.x.initialDelta));

                    currentPercentComplete = Math.round((currentWidth / maxPercentWidth) * 100);

                    updateElement(currentWidth);

                    tooltipTop = taskElementOffset.top - timelineOffset.top;
                    tooltipLeft = taskElementOffset.left + currentWidth - timelineOffset.left;

                    that.view()._updatePercentCompleteTooltip(tooltipTop, tooltipLeft, currentPercentComplete);
                }, 15))
                .bind("dragend", function (e) {
                    that.trigger("percentResizeEnd", { task: task, percentComplete: currentPercentComplete / 100 });

                    cleanUp();
                })
                .bind("dragcancel", function (e) {
                    updateElement(originalPercentWidth);

                    cleanUp();
                })
                .userEvents.bind("select", function (e) {
                    blurActiveElement();
                });
        },

        _createDependencyDraggable: function () {
            var that = this;
            var originalHandle;
            var hoveredHandle = $();
            var hoveredTask = $();
            var dragInProgress;
            var startX;
            var startY;
            var content;
            var contentOffset;
            var useVML = browser.msie && browser.version < 9;
            var styles = GanttTimeline.styles;

            var cleanUp = function () {
                originalHandle
                    .css("display", "")
                    .removeClass(styles.hovered);

                originalHandle.parent().removeClass(styles.origin);
                originalHandle = null;

                toggleHandles(false);

                hoveredTask = $();
                hoveredHandle = $();

                that.view()._removeDependencyDragHint();

                dragInProgress = false;
            };

            var toggleHandles = function (value) {
                if (!hoveredTask.hasClass(styles.origin)) {
                    hoveredTask.find(DOT + styles.taskDot).css("display", value ? "block" : "");
                    hoveredHandle.toggleClass(styles.hovered, value);
                }
            };

            if (!this.options.editable) {
                return;
            }

            if (useVML && document.namespaces) {
                document.namespaces.add("kvml", "urn:schemas-microsoft-com:vml", "#default#VML");
            }

            this._dependencyDraggable = new kendo.ui.Draggable(this.wrapper, {
                distance: 0,
                filter: DOT + styles.taskDot,
                holdToDrag: false
            });

            this._dependencyDraggable
                .bind("dragstart", function (e) {
                    originalHandle = e.currentTarget
                        .css("display", "block")
                        .addClass(styles.hovered);

                    originalHandle.parent().addClass(styles.origin);

                    var elementOffset = originalHandle.offset();

                    content = that.view().content;
                    contentOffset = content.offset();

                    startX = Math.round(elementOffset.left + content.scrollLeft() - contentOffset.left + (originalHandle.outerHeight() / 2));
                    startY = Math.round(elementOffset.top + content.scrollTop() - contentOffset.top + (originalHandle.outerWidth() / 2));

                    dragInProgress = true;
                })
                .bind("drag", kendo.throttle(function (e) {
                    if (!dragInProgress) {
                        return;
                    }

                    that.view()._removeDependencyDragHint();

                    var target = $(kendo.elementUnderCursor(e));
                    var currentX = e.x.location + content.scrollLeft() - contentOffset.left;
                    var currentY = e.y.location + content.scrollTop() - contentOffset.top;

                    that.view()._updateDependencyDragHint({ x: startX, y: startY }, { x: currentX, y: currentY }, useVML);

                    toggleHandles(false);

                    hoveredHandle = (target.hasClass(styles.taskDot)) ? target : $();
                    hoveredTask = target.closest(DOT + styles.taskWrap);

                    toggleHandles(true);
                }, 15))
                .bind("dragend", function (e) {
                    if (hoveredHandle.length) {
                        var fromStart = originalHandle.hasClass(styles.taskDotStart);
                        var toStart = hoveredHandle.hasClass(styles.taskDotStart);

                        var type = fromStart ? (toStart ? 3 : 2) : (toStart ? 1 : 0);

                        var predecessor = that._taskByUid(originalHandle.siblings(DOT + styles.task).attr("data-uid"));
                        var successor = that._taskByUid(hoveredHandle.siblings(DOT + styles.task).attr("data-uid"));

                        if (predecessor !== successor) {
                            that.trigger("dependencyDragEnd", { type: type, predecessor: predecessor, successor: successor });
                        }
                    }

                    cleanUp();
                })
                .bind("dragcancel", function (e) {
                    cleanUp();
                })
                .userEvents.bind("select", function (e) {
                    blurActiveElement();
                });
        },

        _selectable: function () {
            var that = this;
            var styles = GanttTimeline.styles;

            if (this.options.selectable) {
                this.wrapper
                    .on(CLICK + NS, DOT + styles.task, function (e) {
                        e.stopPropagation();

                        if (!e.ctrlKey) {
                            that.trigger("select", { uid: $(this).attr("data-uid") });
                        } else {
                            that.trigger("clear");
                        }
                    })
                    .on(CLICK + NS, DOT + styles.tasksWrapper, function (e) {
                        if (that.selectDependency().length > 0) {
                            that.clearSelection();
                        } else {
                            that.trigger("clear");
                        }
                    })
                    .on(CLICK + NS, DOT + styles.line, function (e) {
                        e.stopPropagation();

                        that.selectDependency(this);
                    });
            }
        },

        select: function (value) {
            var element = this.wrapper.find(value);
            var styles = GanttTimeline.styles;

            if (element.length) {
                this.clearSelection();

                element.addClass(styles.selected);

                if (kendo.support.mobileOS) {
                    element.parent().addClass(styles.taskWrapActive);
                }

                return;
            }

            return this.wrapper.find(DOT + styles.task + DOT + styles.selected);
        },

        selectDependency: function (value) {
            var element = this.wrapper.find(value);
            var uid;
            var styles = GanttTimeline.styles;

            if (element.length) {
                this.clearSelection();
                this.trigger("clear");

                uid = $(element).attr("data-uid");

                this.wrapper.find(DOT + styles.line + "[data-uid='" + uid + "']").addClass(styles.selected);

                return;
            }

            return this.wrapper.find(DOT + styles.line + DOT + styles.selected);
        },

        clearSelection: function () {
            var styles = GanttTimeline.styles;

            this.wrapper
                .find(DOT + styles.selected)
                .removeClass(styles.selected);

            if (kendo.support.mobileOS) {
                this.wrapper
                    .find(DOT + styles.taskWrapActive)
                    .removeClass(styles.taskWrapActive);
            }
        },

        _attachEvents: function () {
            var that = this;
            var styles = GanttTimeline.styles;

            if (this.options.editable) {
                this._tabindex();

                this.wrapper
                    .on(CLICK + NS, DOT + styles.taskDelete, function (e) {
                        that.trigger("removeTask", { uid: $(this).closest(DOT + styles.task).attr("data-uid") });
                        e.stopPropagation();
                        e.preventDefault();
                    })
                    .on(DBLCLICK + NS, DOT + styles.task, function (e) {
                        that.trigger("editTask", { uid: $(this).attr("data-uid") });
                        e.stopPropagation();
                        e.preventDefault();
                    })
                    .on(KEYDOWN + NS, function (e) {
                        var selectedDependency;

                        if (e.keyCode === keys.DELETE) {
                            selectedDependency = that.selectDependency();

                            if (selectedDependency.length) {
                                that.trigger("removeDependency", { uid: selectedDependency.attr("data-uid") });
                                that.clearSelection();
                            }
                        }
                    });
            }
        }
    });

    extend(true, GanttTimeline, { styles: timelineStyles });

})(window.kendo.jQuery);




(function ($, undefined) {

    var kendo = window.kendo;
    var browser = kendo.support.browser;
    var Observable = kendo.Observable;
    var Widget = kendo.ui.Widget;
    var DataSource = kendo.data.DataSource;
    var ObservableObject = kendo.data.ObservableObject;
    var ObservableArray = kendo.data.ObservableArray;
    var Query = kendo.data.Query;
    var isArray = $.isArray;
    var inArray = $.inArray;
    var isFunction = kendo.isFunction;
    var proxy = $.proxy;
    var extend = $.extend;
    var isPlainObject = $.isPlainObject;
    var map = $.map;
    var keys = kendo.keys;
    var NS = ".kendoGantt";
    var PERCENTAGE_FORMAT = "p0";
    var TABINDEX = "tabIndex";
    var CLICK = "click";
    var WIDTH = "width";
    var STRING = "string";
    var DIRECTIONS = {
        "down": {
            origin: "bottom center",
            position: "top center"
        },
        "up": {
            origin: "top center",
            position: "bottom center"
        }
    };
    var ARIA_DESCENDANT = "aria-activedescendant";
    var ACTIVE_CELL = "gantt_active_cell";
    var ACTIVE_OPTION = "action-option-focused";
    var DOT = ".";
    var TASK_DELETE_CONFIRM = "Are you sure you want to delete this task?";
    var DEPENDENCY_DELETE_CONFIRM = "Are you sure you want to delete this dependency?";
    var BUTTON_TEMPLATE = '<button class="#=styles.button# #=className#" ' +
            '#if (action) {#' +
                'data-action="#=action#"' +
            '#}#' +
        '><span class="#=iconClass#"></span>#=text#</button>';
    var COMMAND_BUTTON_TEMPLATE = '<a class="#=className#" #=attr# href="\\#">#=text#</a>';
    var HEADER_VIEWS_TEMPLATE = kendo.template('<ul class="#=styles.viewsWrapper#">' +
            '#for(var view in views){#' +
                '<li class="#=styles.viewButtonDefault# #=styles.viewButton#-#= view.toLowerCase() #" data-#=ns#name="#=view#"><a href="\\#" class="#=styles.link#">#=views[view].title#</a></li>' +
            '#}#' +
        '</ul>');
    var TASK_DROPDOWN_TEMPLATE = kendo.template('<div class="#=styles.popupWrapper#">' +
            '<ul class="#=styles.popupList#" role="listbox">' +
                '#for(var i = 0, l = actions.length; i < l; i++){#' +
                    '<li class="#=styles.item#" data-action="#=actions[i].data#" role="option">#=actions[i].text#</span>' +
                '#}#' +
            '</ul>' +
        '</div>');
    var FOOTER_TEMPLATE = kendo.template('<div class="#=styles.footerWrapper#">' +
            '<div class="#=styles.actions#">' +
                    '<button class="#=styles.button#" data-action="#=action.data#"><span class="#=styles.iconPlus#"></span>#=action.title#</button>' +
            '</div>' +
        '</div>');

    var DATERANGEEDITOR = function (container, options) {
        var attr = { name: options.field };
        var validationRules = options.model.fields[options.field].validation;

        if (validationRules && isPlainObject(validationRules) && validationRules.message) {
            attr[kendo.attr("dateCompare-msg")] = validationRules.message;
        }

        $('<input type="text" required ' +
                kendo.attr("type") + '="date" ' +
                kendo.attr("role") + '="datetimepicker" ' +
                kendo.attr("bind") + '="value:' +
                options.field + '" ' +
                kendo.attr("validate") + "='true' />")
            .attr(attr)
            .appendTo(container);

        $('<span ' + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>')
            .hide()
            .appendTo(container);
    };

    var RESOURCESEDITOR = function (container, options) {
        $('<a href="#" class="' + options.styles.button + '">' + options.messages.assingButton + '</a>').click(options.click).appendTo(container);
    };

    var ganttStyles = {
        wrapper: "k-widget k-gantt",
        listWrapper: "k-gantt-layout k-gantt-treelist",
        list: "k-gantt-treelist",
        timelineWrapper: "k-gantt-layout k-gantt-timeline",
        timeline: "k-gantt-timeline",
        splitBarWrapper: "k-splitbar k-state-default k-splitbar-horizontal k-splitbar-draggable-horizontal k-gantt-layout",
        splitBar: "k-splitbar",
        splitBarHover: "k-splitbar-horizontal-hover",
        popupWrapper: "k-list-container",
        popupList: "k-list k-reset",
        resizeHandle: "k-resize-handle",
        icon: "k-icon",
        item: "k-item",
        line: "k-line",
        buttonDelete: "k-gantt-delete",
        buttonCancel: "k-gantt-cancel",
        buttonSave: "k-gantt-update",
        primary: "k-primary",
        hovered: "k-state-hover",
        selected: "k-state-selected",
        focused: "k-state-focused",
        gridHeader: "k-grid-header",
        gridHeaderWrap: "k-grid-header-wrap",
        gridContent: "k-grid-content",
        popup: {
            form: "k-popup-edit-form",
            editForm: "k-gantt-edit-form",
            formContainer: "k-edit-form-container",
            resourcesFormContainer: "k-resources-form-container",
            message: "k-popup-message",
            buttonsContainer: "k-edit-buttons k-state-default",
            button: "k-button",
            editField: "k-edit-field",
            editLabel: "k-edit-label",
            resourcesField: "k-gantt-resources"
        },
        toolbar: {
            headerWrapper: "k-floatwrap k-header k-gantt-toolbar",
            footerWrapper: "k-floatwrap k-header k-gantt-toolbar",
            toolbar: "k-gantt-toolbar",
            views: "k-gantt-views",
            viewsWrapper: "k-reset k-header k-gantt-views",
            actions: "k-gantt-actions",
            button: "k-button k-button-icontext",
            iconPlus: "k-icon k-i-plus",
            iconPdf: "k-icon k-i-pdf",
            viewButtonDefault: "k-state-default",
            viewButton: "k-view",
            link: "k-link",
            pdfButton: "k-gantt-pdf",
            appendButton: "k-gantt-create"
        }
    };

    function selector(uid) {
        return "[" + kendo.attr("uid") + (uid ? "='" + uid + "']" : "]");
    }

    function trimOptions(options) {
        delete options.name;
        delete options.prefix;

        delete options.remove;
        delete options.edit;
        delete options.add;
        delete options.navigate;

        return options;
    }

    function dateCompareValidator(input) {
        if (input.filter("[name=end], [name=start]").length) {
            var field = input.attr("name");
            var picker = kendo.widgetInstance(input, kendo.ui);
            var dates = {};
            var container = input;
            var editable;
            var model;

            while (container !== window && !editable) {
                container = container.parent();

                editable = container.data("kendoEditable");
            }

            model = editable ? editable.options.model : null;

            if (!model) {
                return true;
            }

            dates.start = model.start;
            dates.end = model.end;
            dates[field] = picker ? picker.value() : kendo.parseDate(input.value());

            return dates.start <= dates.end;
        }

        return true;
    }

    function focusTable(table, direct) {
        var wrapper = table.parents('[' + kendo.attr("role") + '="gantt"]');
        var scrollPositions = [];
        var parents = scrollableParents(wrapper);

        table.attr(TABINDEX, 0);

        if (direct) {
            parents.each(function (index, parent) {
                scrollPositions[index] = $(parent).scrollTop();
            });
        }

        try {
            //The setActive method does not cause the document to scroll to the active object in the current page
            table[0].setActive();
        } catch (e) {
            table[0].focus();
        }

        if (direct) {
            parents.each(function (index, parent) {
                $(parent).scrollTop(scrollPositions[index]);
            });
        }
    }

    function scrollableParents(element) {
        return $(element).parentsUntil("body")
                .filter(function (index, element) {
                    var computedStyle = kendo.getComputedStyles(element, ["overflow"]);
                    return computedStyle.overflow != "visible";
                })
                .add(window);
    }

    var defaultCommands = {
        append: {
            text: "Add Task",
            action: "add",
            className: ganttStyles.toolbar.appendButton,
            iconClass: ganttStyles.toolbar.iconPlus
        },
        pdf: {
            text: "Export to PDF",
            className: ganttStyles.toolbar.pdfButton,
            iconClass: ganttStyles.toolbar.iconPdf
        }
    };

    var TaskDropDown = Observable.extend({
        init: function (element, options) {

            Observable.fn.init.call(this);

            this.element = element;
            this.options = extend(true, {}, this.options, options);

            this._popup();
        },

        options: {
            direction: "down",
            navigatable: false
        },

        _current: function (method) {
            var ganttStyles = Gantt.styles;
            var current = this.list
                .find(DOT + ganttStyles.focused);
            var sibling = current[method]();

            if (sibling.length) {
                current
                    .removeClass(ganttStyles.focused)
                    .removeAttr("id");
                sibling
                    .addClass(ganttStyles.focused)
                    .attr("id", ACTIVE_OPTION);

                this.list.find("ul")
                    .removeAttr(ARIA_DESCENDANT)
                    .attr(ARIA_DESCENDANT, ACTIVE_OPTION);
            }
        },

        _popup: function () {
            var that = this;
            var ganttStyles = Gantt.styles;
            var itemSelector = "li" + DOT + ganttStyles.item;
            var actions = this.options.messages.actions;
            var navigatable = this.options.navigatable;

            this.list = $(TASK_DROPDOWN_TEMPLATE({
                styles: ganttStyles,
                actions: [
                    {
                        data: "add",
                        text: actions.addChild
                    },
                    {
                        data: "insert-before",
                        text: actions.insertBefore
                    },
                    {
                        data: "insert-after",
                        text: actions.insertAfter
                    }
                ]
            }));

            this.element.append(this.list);

            this.popup = new kendo.ui.Popup(this.list,
                extend({
                    anchor: this.element,
                    open: function (e) {
                        that._adjustListWidth();
                    },
                    animation: this.options.animation
                }, DIRECTIONS[this.options.direction])
            );

            this.element
                .on(CLICK + NS, DOT + ganttStyles.toolbar.appendButton, function (e) {
                    var target = $(this);
                    var action = target.attr(kendo.attr("action"));

                    e.preventDefault();

                    if (action) {
                        that.trigger("command", { type: action });
                    } else {
                        that.popup.open();

                        if (navigatable) {
                            that.list
                                .find("li:first")
                                .addClass(ganttStyles.focused)
                                .attr("id", ACTIVE_OPTION)
                                .end()
                                .find("ul")
                                .attr({
                                    TABINDEX: 0,
                                    "aria-activedescendant": ACTIVE_OPTION
                                })
                                .focus();
                        }
                    }
                });

            this.list
                .find(itemSelector)
                .hover(function () {
                    $(this).addClass(ganttStyles.hovered);
                }, function () {
                    $(this).removeClass(ganttStyles.hovered);
                })
                .end()
                .on(CLICK + NS, itemSelector, function (e) {
                    that.trigger("command", { type: $(this).attr(kendo.attr("action")) });
                    that.popup.close();
                });

            if (navigatable) {
                this.popup
                    .bind("close", function () {
                        that.list
                            .find(itemSelector)
                            .removeClass(ganttStyles.focused)
                            .end()
                            .find("ul")
                            .attr(TABINDEX, 0);

                        that.element
                            .parents('[' + kendo.attr("role") + '="gantt"]')
                            .find(DOT + ganttStyles.gridContent + " > table:first")
                            .focus();
                    });

                this.list
                    .find("ul")
                    .on("keydown" + NS, function (e) {
                        var key = e.keyCode;

                        switch (key) {
                            case keys.UP:
                                e.preventDefault();
                                that._current("prev");
                                break;
                            case keys.DOWN:
                                e.preventDefault();
                                that._current("next");
                                break;
                            case keys.ENTER:
                                that.list
                                    .find(DOT + ganttStyles.focused)
                                    .click();
                                break;
                            case keys.ESC:
                                e.preventDefault();
                                that.popup.close();
                                break;
                        }
                    });
            }
        },

        _adjustListWidth: function () {
            var list = this.list;
            var width = list[0].style.width;
            var wrapper = this.element;
            var computedStyle;
            var computedWidth;

            if (!list.data(WIDTH) && width) {
                return;
            }

            computedStyle = window.getComputedStyle ? window.getComputedStyle(wrapper[0], null) : 0;
            computedWidth = computedStyle ? parseFloat(computedStyle.width) : wrapper.outerWidth();

            if (computedStyle && (browser.mozilla || browser.msie)) { // getComputedStyle returns different box in FF and IE.
                computedWidth += parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight) + parseFloat(computedStyle.borderLeftWidth) + parseFloat(computedStyle.borderRightWidth);
            }

            if (list.css("box-sizing") !== "border-box") {
                width = computedWidth - (list.outerWidth() - list.width());
            } else {
                width = computedWidth;
            }

            list.css({
                fontFamily: wrapper.css("font-family"),
                width: width
            })
            .data(WIDTH, width);
        },

        destroy: function () {
            this.popup.destroy();
            this.element.off(NS);
            this.list.off(NS);
            this.unbind();
        }
    });

    var createDataSource = function (type, name) {
        return function (options) {
            options = isArray(dataSource) ? { data: options} : options;

            var dataSource = options || {};
            var data = dataSource.data;

            dataSource.data = data;

            if (!(dataSource instanceof type) && dataSource instanceof DataSource) {
                throw new Error("Incorrect DataSource type. Only " + name + " instances are supported");
            }

            return dataSource instanceof type ? dataSource : new type(dataSource);
        };
    };

    var GanttDependency = kendo.data.Model.define({
        id: "id",
        fields: {
            id: { type: "number" },
            predecessorId: { type: "number" },
            successorId: { type: "number" },
            type: { type: "number" }
        }
    });

    var GanttDependencyDataSource = DataSource.extend({
        init: function (options) {
            DataSource.fn.init.call(this, extend(true, {}, {
                schema: {
                    modelBase: GanttDependency,
                    model: GanttDependency
                }
            }, options));
        },

        successors: function (id) {
            return this._dependencies("predecessorId", id);
        },

        predecessors: function (id) {
            return this._dependencies("successorId", id);
        },

        dependencies: function (id) {
            var predecessors = this.predecessors(id);
            var successors = this.successors(id);

            predecessors.push.apply(predecessors, successors);

            return predecessors;
        },

        _dependencies: function (field, id) {
            var data = this.view();
            var filter = {
                field: field,
                operator: "eq",
                value: id
            };

            data = new Query(data).filter(filter).toArray();

            return data;
        }
    });

    GanttDependencyDataSource.create = createDataSource(GanttDependencyDataSource, "GanttDependencyDataSource");

    var GanttTask = kendo.data.Model.define({

        duration: function () {
            var end = this.end;
            var start = this.start;

            return end - start;
        },

        isMilestone: function () {
            return this.duration() === 0;
        },

        _offset: function (value) {
            var field = ["start", "end"];
            var newValue;

            for (var i = 0; i < field.length; i++) {
                newValue = new Date(this.get(field[i]).getTime() + value);
                this.set(field[i], newValue);
            }
        },

        id: "id",
        fields: {
            id: { type: "number" },
            parentId: { type: "number", defaultValue: null, validation: { required: true} },
            orderId: { type: "number", validation: { required: true} },
            title: { type: "string", defaultValue: "" },
            start: {
                type: "date", validation: {
                    required: true,
                    dateCompare: dateCompareValidator,
                    message: "Start date should be before or equal to the end date"
                }
            },
            end: {
                type: "date", validation: {
                    required: true,
                    dateCompare: dateCompareValidator,
                    message: "End date should be after or equal to the start date"
                }
            },
            percentComplete: { type: "number", validation: { required: true, min: 0, max: 1, step: 0.01} },
            summary: { type: "boolean" },
            expanded: { type: "boolean", defaultValue: true }
        }
    });

    var GanttDataSource = DataSource.extend({
        init: function (options) {
            DataSource.fn.init.call(this, extend(true, {}, {
                schema: {
                    modelBase: GanttTask,
                    model: GanttTask
                }
            }, options));
        },

        remove: function (task) {
            var parentId = task.get("parentId");
            var children = this.taskAllChildren(task);

            this._removeItems(children);

            task = DataSource.fn.remove.call(this, task);

            this._childRemoved(parentId, task.get("orderId"));

            return task;
        },

        add: function (task) {
            if (!task) {
                return;
            }

            task = this._toGanttTask(task);

            return this.insert(this.taskSiblings(task).length, task);
        },

        insert: function (index, task) {
            if (!task) {
                return;
            }

            task = this._toGanttTask(task);

            task.set("orderId", index);

            task = DataSource.fn.insert.call(this, index, task);

            this._reorderSiblings(task, this.taskSiblings(task).length - 1);
            this._resolveSummaryFields(this.taskParent(task));

            return task;
        },

        taskChildren: function (task) {
            var data = this.view();
            var filter = {
                field: "parentId",
                operator: "eq",
                value: null
            };
            var order = this._sort || {
                field: "orderId",
                dir: "asc"
            };
            var taskId;

            if (!!task) {
                taskId = task.get("id");

                if (taskId === undefined || taskId === null) {
                    return [];
                }

                filter.value = taskId;
            }

            data = new Query(data).filter(filter).sort(order).toArray();

            return data;
        },

        taskAllChildren: function (task) {
            var data = [];
            var that = this;
            var callback = function (task) {
                var tasks = that.taskChildren(task);

                data.push.apply(data, tasks);
                map(tasks, callback);
            };

            if (!!task) {
                callback(task);
            } else {
                data = this.view();
            }

            return data;
        },

        taskSiblings: function (task) {
            if (!task) {
                return null;
            }

            var parent = this.taskParent(task);

            return this.taskChildren(parent);
        },

        taskParent: function (task) {
            if (!task || task.get("parentId") === null) {
                return null;
            }
            return this.get(task.parentId);
        },

        taskLevel: function (task) {
            var level = 0;
            var parent = this.taskParent(task);

            while (parent !== null) {
                level += 1;
                parent = this.taskParent(parent);
            }

            return level;
        },

        taskTree: function (task) {
            var data = [];
            var current;
            var tasks = this.taskChildren(task);

            for (var i = 0, l = tasks.length; i < l; i++) {
                current = tasks[i];
                data.push(current);

                if (current.get("expanded")) {
                    var children = this.taskTree(current);

                    data.push.apply(data, children);
                }
            }

            return data;
        },

        update: function (task, taskInfo) {
            var that = this;
            var oldValue;

            var offsetChildren = function (parentTask, offset) {
                var children = that.taskAllChildren(parentTask);

                for (var i = 0, l = children.length; i < l; i++) {
                    children[i]._offset(offset);
                }
            };

            var modelChangeHandler = function (e) {
                var field = e.field;
                var model = e.sender;

                switch (field) {
                    case "start":
                        that._resolveSummaryStart(that.taskParent(model));

                        offsetChildren(model, model.get(field).getTime() - oldValue.getTime());
                        break;
                    case "end":
                        that._resolveSummaryEnd(that.taskParent(model));
                        break;
                    case "percentComplete":
                        that._resolveSummaryPercentComplete(that.taskParent(model));
                        break;
                    case "orderId":
                        that._reorderSiblings(model, oldValue);
                        break;
                }
            };

            if (taskInfo.parentId !== undefined) {

                oldValue = task.get("parentId");

                if (oldValue !== taskInfo.parentId) {
                    task.set("parentId", taskInfo.parentId);

                    that._childRemoved(oldValue, task.get("orderId"));

                    task.set("orderId", that.taskSiblings(task).length - 1);
                    that._resolveSummaryFields(that.taskParent(task));
                }

                delete taskInfo.parentId;
            }

            task.bind("change", modelChangeHandler);

            for (var field in taskInfo) {
                oldValue = task.get(field);
                task.set(field, taskInfo[field]);
            }

            task.unbind("change", modelChangeHandler);
        },

        _resolveSummaryFields: function (summary) {
            if (!summary) {
                return;
            }

            this._updateSummary(summary);

            if (!this.taskChildren(summary).length) {
                return;
            }

            this._resolveSummaryStart(summary);
            this._resolveSummaryEnd(summary);
            this._resolveSummaryPercentComplete(summary);
        },

        _resolveSummaryStart: function (summary) {
            var that = this;
            var getSummaryStart = function (parentTask) {
                var children = that.taskChildren(parentTask);
                var min = children[0].start.getTime();
                var currentMin;

                for (var i = 1, l = children.length; i < l; i++) {
                    currentMin = children[i].start.getTime();
                    if (currentMin < min) {
                        min = currentMin;
                    }
                }

                return new Date(min);
            };

            this._updateSummaryRecursive(summary, "start", getSummaryStart);
        },

        _resolveSummaryEnd: function (summary) {
            var that = this;
            var getSummaryEnd = function (parentTask) {
                var children = that.taskChildren(parentTask);
                var max = children[0].end.getTime();
                var currentMax;

                for (var i = 1, l = children.length; i < l; i++) {
                    currentMax = children[i].end.getTime();
                    if (currentMax > max) {
                        max = currentMax;
                    }
                }

                return new Date(max);
            };

            this._updateSummaryRecursive(summary, "end", getSummaryEnd);
        },

        _resolveSummaryPercentComplete: function (summary) {
            var that = this;
            var getSummaryPercentComplete = function (parentTask) {
                var children = that.taskChildren(parentTask);
                var percentComplete = new Query(children).aggregate([{
                    field: "percentComplete",
                    aggregate: "average"
                }]);

                return percentComplete.percentComplete.average;
            };

            this._updateSummaryRecursive(summary, "percentComplete", getSummaryPercentComplete);
        },

        _updateSummaryRecursive: function (summary, field, callback) {
            if (!summary) {
                return;
            }

            var value = callback(summary);

            summary.set(field, value);

            var parent = this.taskParent(summary);

            if (parent) {
                this._updateSummaryRecursive(parent, field, callback);
            }
        },

        _childRemoved: function (parentId, index) {
            var parent = parentId === null ? null : this.get(parentId);
            var children = this.taskChildren(parent);

            for (var i = index, l = children.length; i < l; i++) {
                children[i].set("orderId", i);
            }

            this._resolveSummaryFields(parent);
        },

        _reorderSiblings: function (task, oldOrderId) {
            var orderId = task.get("orderId");
            var direction = orderId > oldOrderId;
            var startIndex = direction ? oldOrderId : orderId;
            var endIndex = direction ? orderId : oldOrderId;
            var newIndex = direction ? startIndex : startIndex + 1;
            var siblings = this.taskSiblings(task);

            endIndex = Math.min(endIndex, siblings.length - 1);

            for (var i = startIndex; i <= endIndex; i++) {
                if (siblings[i] === task) {
                    continue;
                }

                siblings[i].set("orderId", newIndex);

                newIndex += 1;
            }
        },

        _updateSummary: function (task) {
            if (task !== null) {
                var childCount = this.taskChildren(task).length;

                task.set("summary", childCount > 0);
            }
        },

        _toGanttTask: function (task) {
            if (!(task instanceof GanttTask)) {
                var taskInfo = task;

                task = this._createNewModel();
                task.accept(taskInfo);
            }

            return task;
        }
    });

    GanttDataSource.create = createDataSource(GanttDataSource, "GanttDataSource");

    extend(true, kendo.data, {
        GanttDataSource: GanttDataSource,
        GanttTask: GanttTask,
        GanttDependencyDataSource: GanttDependencyDataSource,
        GanttDependency: GanttDependency
    });

    var editors = {
        desktop: {
            dateRange: DATERANGEEDITOR,
            resources: RESOURCESEDITOR
        }
    };

    var Editor = kendo.Observable.extend({
        init: function (element, options) {
            kendo.Observable.fn.init.call(this);

            this.element = element;
            this.options = extend(true, {}, this.options, options);
            this.createButton = this.options.createButton;
        },

        fields: function (editors, model) {
            var that = this;
            var messages = this.options.messages.editor;
            var resources = this.options.resources;

            var click = function (e) {
                e.preventDefault();
                resources.editor(that.container.find(DOT + Gantt.styles.popup.resourcesField), model);
            };

            var fields = [
                { field: "title", title: messages.title },
                { field: "start", title: messages.start, editor: editors.dateRange },
                { field: "end", title: messages.end, editor: editors.dateRange },
                { field: "percentComplete", title: messages.percentComplete, format: PERCENTAGE_FORMAT }
            ];

            if (model.get(resources.field)) {
                fields.push({ field: resources.field, title: messages.resources, messages: messages, editor: editors.resources, click: click, styles: Gantt.styles.popup });
            }

            return fields;
        },

        _buildEditTemplate: function (model, fields, editableFields) {
            var resources = this.options.resources;
            var template = this.options.editable.template;
            var settings = extend({}, kendo.Template, this.options.templateSettings);
            var paramName = settings.paramName;
            var popupStyles = Gantt.styles.popup;
            var html = "";

            if (template) {
                if (typeof template === STRING) {
                    template = window.unescape(template);
                }

                html += (kendo.template(template, settings))(model);
            } else {
                for (var i = 0, length = fields.length; i < length; i++) {
                    var field = fields[i];

                    html += '<div class="' + popupStyles.editLabel + '"><label for="' + field.field + '">' + (field.title || field.field || "") + '</label></div>';

                    if (field.field === resources.field) {
                        html += '<div class="' + popupStyles.resourcesField + '" style="display:none"></div>';
                    }

                    if ((!model.editable || model.editable(field.field))) {
                        editableFields.push(field);
                        html += '<div ' + kendo.attr("container-for") + '="' + field.field + '" class="' + popupStyles.editField + '"></div>';
                    } else {
                        var tmpl = "#:";

                        if (field.field) {
                            field = kendo.expr(field.field, paramName);
                            tmpl += field + "==null?'':" + field;
                        } else {
                            tmpl += "''";
                        }

                        tmpl += "#";

                        tmpl = kendo.template(tmpl, settings);

                        html += '<div class="' + popupStyles.editField + '">' + tmpl(model) + '</div>';
                    }
                }
            }

            return html;
        }
    });

    var PopupEditor = Editor.extend({
        destroy: function () {
            this.close();
            this.unbind();
        },

        editTask: function (task) {
            this.editable = this._createPopupEditor(task);
        },

        close: function () {
            var that = this;

            var destroy = function () {
                if (that.editable) {
                    that.editable.destroy();
                    that.editable = null;
                    that.container = null;
                }

                if (that.popup) {
                    that.popup.destroy();
                    that.popup = null;
                }
            };

            if (this.editable && this.container.is(":visible")) {
                this.container.data("kendoWindow").bind("deactivate", destroy).close();
            } else {
                destroy();
            }
        },

        showDialog: function (options) {
            var buttons = options.buttons;
            var popupStyles = Gantt.styles.popup;

            var html = kendo.format('<div class="{0}"><div class="{1}"><p class="{2}">{3}</p><div class="{4}">',
                popupStyles.form, popupStyles.formContainer, popupStyles.message, options.text, popupStyles.buttonsContainer);

            for (var i = 0, length = buttons.length; i < length; i++) {
                html += this.createButton(buttons[i]);
            }

            html += '</div></div></div>';

            var wrapper = this.element;

            if (this.popup) {
                this.popup.destroy();
            }

            var popup = this.popup = $(html).appendTo(wrapper)
                .eq(0)
                .on("click", DOT + popupStyles.button, function (e) {
                    e.preventDefault();

                    popup.close();

                    var buttonIndex = $(e.currentTarget).index();

                    buttons[buttonIndex].click();
                })
                .kendoWindow({
                    modal: true,
                    resizable: false,
                    draggable: false,
                    title: options.title,
                    visible: false,
                    close: function () {
                        this.destroy();
                        wrapper.focus();
                    }
                })
                .getKendoWindow();

            popup.center().open();
        },

        _createPopupEditor: function (task) {
            var that = this;
            var options = {};
            var messages = this.options.messages;
            var ganttStyles = Gantt.styles;
            var popupStyles = ganttStyles.popup;

            var html = kendo.format('<div {0}="{1}" class="{2} {3}"><div class="{4}">',
                kendo.attr("uid"), task.uid, popupStyles.form, popupStyles.editForm, popupStyles.formContainer);

            var fields = this.fields(editors.desktop, task);
            var editableFields = [];

            html += this._buildEditTemplate(task, fields, editableFields);

            html += '<div class="' + popupStyles.buttonsContainer + '">';
            html += this.createButton({ name: "update", text: messages.save, className: Gantt.styles.primary });
            html += this.createButton({ name: "cancel", text: messages.cancel });
            html += this.createButton({ name: "delete", text: messages.destroy });

            html += '</div></div></div>';

            var container = this.container = $(html).appendTo(this.element)
                .eq(0)
                .kendoWindow(extend({
                    modal: true,
                    resizable: false,
                    draggable: true,
                    title: messages.editor.editorTitle,
                    visible: false,
                    close: function (e) {
                        if (e.userTriggered) {
                            if (that.trigger("cancel", { container: container, model: task })) {
                                e.preventDefault();
                            }
                        }
                    }
                }, options));

            var editableWidget = container
                .kendoEditable({
                    fields: editableFields,
                    model: task,
                    clearContainer: false,
                    validateOnBlur: true,
                    target: that.options.target
                })
                .data("kendoEditable");

            if (!this.trigger("edit", { container: container, model: task })) {
                container.data("kendoWindow").center().open();

                container.on(CLICK + NS, DOT + ganttStyles.buttonCancel, function (e) {
                    e.preventDefault();
                    e.stopPropagation();

                    that.trigger("cancel", { container: container, model: task });
                });

                container.on(CLICK + NS, DOT + ganttStyles.buttonSave, function (e) {
                    e.preventDefault();
                    e.stopPropagation();

                    var fields = that.fields(editors.desktop, task);
                    var updateInfo = {};
                    var field;

                    for (var i = 0, length = fields.length; i < length; i++) {
                        field = fields[i].field;
                        updateInfo[field] = task.get(field);
                    }

                    that.trigger("save", { container: container, model: task, updateInfo: updateInfo });
                });

                container.on(CLICK + NS, DOT + ganttStyles.buttonDelete, function (e) {
                    e.preventDefault();
                    e.stopPropagation();

                    that.trigger("remove", { container: container, model: task });
                });
            } else {
                that.trigger("cancel", { container: container, model: task });
            }

            return editableWidget;
        }
    });

    var ResourceEditor = Widget.extend({
        init: function (element, options) {
            Widget.fn.init.call(this, element, options);

            this.wrapper = this.element;
            this.model = this.options.model;
            this.resourcesField = this.options.resourcesField;
            this.createButton = this.options.createButton;

            this._initContainer();
            this._attachHandlers();
        },

        events: [
            "save"
        ],

        open: function () {
            this.window.center().open();
            this.grid.resize(true);
        },

        close: function () {
            this.window.bind("deactivate", proxy(this.destroy, this)).close();
        },

        destroy: function () {
            this._dettachHandlers();

            this.grid.destroy();
            this.grid = null;

            this.window.destroy();
            this.window = null;

            Widget.fn.destroy.call(this);

            kendo.destroy(this.wrapper);

            this.element = this.wrapper = null;
        },

        _attachHandlers: function () {
            var ganttStyles = Gantt.styles;
            var grid = this.grid;

            var closeHandler = this._cancelProxy = proxy(this._cancel, this);
            this.container.on(CLICK + NS, DOT + ganttStyles.buttonCancel, this._cancelProxy);

            this._saveProxy = proxy(this._save, this);
            this.container.on(CLICK + NS, DOT + ganttStyles.buttonSave, this._saveProxy);

            this.window.bind("close", function (e) {
                if (e.userTriggered) {
                    closeHandler(e);
                }
            });

            grid.wrapper.on(CLICK + NS, "input[type='checkbox']", function (e) {
                var element = $(this);
                var row = $(element).closest("tr");
                var model = grid.dataSource.getByUid(row.attr(kendo.attr("uid")));
                var value = $(element).is(":checked") ? 1 : "";

                model.set("value", value);
            });
        },

        _dettachHandlers: function () {
            this._cancelProxy = null;
            this._saveProxy = null;
            this.container.off(NS);
            this.grid.wrapper.off();
        },

        _cancel: function (e) {
            e.preventDefault();
            this.close();
        },

        _save: function (e) {
            e.preventDefault();

            this._updateModel();

            if (!this.wrapper.is(DOT + Gantt.styles.popup.resourcesField)) {
                this.trigger("save", { container: this.wrapper, model: this.model });
            }

            this.close();
        },

        _initContainer: function () {
            var popupStyles = Gantt.styles.popup;
            var dom = kendo.format('<div class="{0} {1}"><div class="{2} {3}"/></div>"',
                popupStyles.form, popupStyles.editForm, popupStyles.formContainer, popupStyles.resourcesFormContainer);

            dom = $(dom);

            this.container = dom.find(DOT + popupStyles.resourcesFormContainer);

            this.window = dom.kendoWindow({
                modal: true,
                resizable: false,
                draggable: true,
                visible: false,
                title: this.options.messages.resourcesEditorTitle
            }).data("kendoWindow");

            this._resourceGrid();
            this._createButtons();
        },

        _resourceGrid: function () {
            var that = this;
            var messages = this.options.messages;
            var element = $('<div id="resources-grid"/>').appendTo(this.container);

            this.grid = new kendo.ui.Grid(element, {
                columns: [
                    {
                        field: "name",
                        title: messages.resourcesHeader,
                        template:
                            "<label><input type='checkbox' value='#=name#'" +
                                "# if (value > 0 && value !== null) {#" +
                                       "checked='checked'" +
                                "# } #" +
                            "/>#=name#</labe>"
                    },
                    {
                        field: "value",
                        title: messages.unitsHeader,
                        template: function (dataItem) {
                            var valueFormat = dataItem.format;
                            var value = dataItem.value !== null ? dataItem.value : "";

                            return valueFormat ? kendo.toString(value, valueFormat) : value;
                        }
                    }
                ],
                height: 280,
                sortable: true,
                editable: true,
                filterable: true,
                dataSource: {
                    data: that.options.data,
                    schema: {
                        model: {
                            id: "id",
                            fields: {
                                id: { from: "id", type: "number" },
                                name: { from: "name", type: "string", editable: false },
                                value: { from: "value", type: "number", defaultValue: "" },
                                format: { from: "format", type: "string" }
                            }
                        }
                    }
                },
                save: function (e) {
                    var value = !!e.values.value;
                    e.container.parent().find("input[type='checkbox']").prop("checked", value);
                }
            });
        },

        _createButtons: function () {
            var buttons = this.options.buttons;
            var html = '<div class="' + Gantt.styles.popup.buttonsContainer + '">';

            for (var i = 0, length = buttons.length; i < length; i++) {
                html += this.createButton(buttons[i]);
            }

            html += "</div>";

            this.container.append(html);
        },

        _updateModel: function () {
            var resources = [];
            var value;
            var data = this.grid.dataSource.data();

            for (var i = 0, length = data.length; i < length; i++) {
                value = data[i].get("value");
                if (value !== null && value > 0) {
                    resources.push(data[i]);
                }
            }

            this.model[this.resourcesField] = resources;
        }
    });

    var Gantt = Widget.extend({
        init: function (element, options) {
            if (isArray(options)) {
                options = { dataSource: options };
            }

            Widget.fn.init.call(this, element, options);

            this._wrapper();

            this._resources();

            this._timeline();

            this._toolbar();

            this._footer();

            this._adjustDimensions();

            // Prevent extra refresh from setting the view
            this._preventRefresh = true;

            this.view(this.timeline._selectedViewName);

            this._preventRefresh = false;

            this._dataSource();

            this._assignments();

            this._dropDowns();

            this._list();

            this._dependencies();

            this._resizable();

            this._scrollable();

            this._dataBind();

            this._attachEvents();

            this._createEditor();

            kendo.notify(this);
        },

        events: [
            "dataBinding",
            "dataBound",
            "add",
            "edit",
            "remove",
            "cancel",
            "save",
            "change",
            "navigate",
            "moveStart",
            "move",
            "moveEnd",
            "resizeStart",
            "resize",
            "resizeEnd"
        ],

        options: {
            name: "Gantt",
            autoBind: true,
            navigatable: false,
            selectable: true,
            editable: true,
            columns: [],
            views: [],
            dataSource: {},
            dependencies: {},
            resources: {},
            assignments: {},
            messages: {
                save: "Save",
                cancel: "Cancel",
                destroy: "Delete",
                deleteTaskWindowTitle: "Delete task",
                deleteDependencyWindowTitle: "Delete dependency",
                views: {
                    day: "Day",
                    week: "Week",
                    month: "Month",
                    year: "Year"
                },
                actions: {
                    append: "Add Task",
                    addChild: "Add Child",
                    insertBefore: "Add Above",
                    insertAfter: "Add Below",
                    pdf: "Export to PDF"
                },
                editor: {
                    editorTitle: "Task",
                    resourcesEditorTitle: "Resources",
                    title: "Title",
                    start: "Start",
                    end: "End",
                    percentComplete: "Complete",
                    resources: "Resources",
                    assingButton: "Assign",
                    resourcesHeader: "Resources",
                    unitsHeader: "Units"
                }
            },
            showWorkHours: true,
            showWorkDays: true,
            workDayStart: new Date(1980, 1, 1, 8, 0, 0),
            workDayEnd: new Date(1980, 1, 1, 17, 0, 0),
            workWeekStart: 1,
            workWeekEnd: 5,
            hourSpan: 1,
            snap: true,
            height: 600,
            listWidth: "30%"
        },

        select: function (value) {
            var list = this.list;

            if (!value) {
                return list.select();
            }

            list.select(value);

            return;
        },

        clearSelection: function () {
            this.list.clearSelection();
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            if (this.dataSource) {
                this.dataSource.unbind("change", this._refreshHandler);
                this.dataSource.unbind("progress", this._progressHandler);
                this.dataSource.unbind("error", this._errorHandler);
            }

            if (this.dependencies) {
                this.dependencies.unbind("change", this._dependencyRefreshHandler);
                this.dependencies.unbind("error", this._dependencyErrorHandler);
            }

            if (this.timeline) {
                this.timeline.unbind();
                this.timeline.destroy();
            }

            if (this.list) {
                this.list.unbind();
                this.list.destroy();
            }

            if (this.footerDropDown) {
                this.footerDropDown.destroy();
            }

            if (this.headerDropDown) {
                this.headerDropDown.destroy();
            }

            if (this._editor) {
                this._editor.destroy();
            }

            if (this._resizeDraggable) {
                this._resizeDraggable.destroy();
            }

            this.toolbar.off(NS);

            $(window).off("resize" + NS, this._resizeHandler);
            $(this.wrapper).off(NS);

            this.toolbar = null;
            this.footer = null;
        },

        _attachEvents: function () {
            this._resizeHandler = proxy(this.resize, this);
            $(window).on("resize" + NS, this._resizeHandler);
        },

        _wrapper: function () {
            var ganttStyles = Gantt.styles;
            var splitBarHandleClassName = [ganttStyles.icon, ganttStyles.resizeHandle].join(" ");
            var options = this.options;
            var height = options.height;
            var width = options.width;

            this.wrapper = this.element
                            .addClass(ganttStyles.wrapper)
                            .append("<div class='" + ganttStyles.listWrapper + "'><div></div></div>")
                            .append("<div class='" + ganttStyles.splitBarWrapper + "'><div class='" + splitBarHandleClassName + "'></div></div>")
                            .append("<div class='" + ganttStyles.timelineWrapper + "'><div></div></div>");

            this.wrapper.find(DOT + ganttStyles.list).width(options.listWidth);

            if (height) {
                this.wrapper.height(height);
            }

            if (width) {
                this.wrapper.width(width);
            }
        },

        _toolbar: function () {
            var that = this;
            var ganttStyles = Gantt.styles;
            var viewsSelector = DOT + ganttStyles.toolbar.views + " > li";
            var pdfSelector = DOT + ganttStyles.toolbar.pdfButton;
            var hoveredClassName = ganttStyles.hovered;
            var actions = this.options.toolbar;
            var actionsWrap = $("<div class='" + ganttStyles.toolbar.actions + "'>");
            var toolbar;
            var views;

            if (!isFunction(actions)) {
                actions = (typeof actions === STRING ? actions : this._actions(actions));
                actions = proxy(kendo.template(actions), this);
            }

            views = $(HEADER_VIEWS_TEMPLATE({
                ns: kendo.ns,
                views: this.timeline.views,
                styles: ganttStyles.toolbar
            }));

            actionsWrap.append(actions({}));

            toolbar = $("<div class='" + ganttStyles.toolbar.headerWrapper + "'>")
                .append(actionsWrap)
                .append(views);

            this.wrapper.prepend(toolbar);
            this.toolbar = toolbar;

            toolbar
                .on(CLICK + NS, viewsSelector, function (e) {
                    e.preventDefault();

                    var name = $(this).attr(kendo.attr("name"));

                    if (!that.trigger("navigate", { view: name })) {
                        that.view(name);
                    }
                })
                .on(CLICK + NS, pdfSelector, function (e) {
                    that.saveAsPDF();
                });

            this.wrapper
                .find(DOT + ganttStyles.toolbar.toolbar + " li")
                .hover(function () {
                    $(this).addClass(hoveredClassName);
                }, function () {
                    $(this).removeClass(hoveredClassName);
                });
        },

        _actions: function () {
            var options = this.options;
            var actions = options.toolbar;
            var html = "";

            if (!isArray(actions)) {
                if (options.editable) {
                    actions = ["append"];
                } else {
                    return html;
                }
            }

            for (var i = 0, length = actions.length; i < length; i++) {
                html += this._createButton(actions[i]);
            }

            return html;
        },

        _footer: function () {
            if (!this.options.editable) {
                return;
            }

            var footer = $(FOOTER_TEMPLATE({
                styles: Gantt.styles.toolbar,
                action: {
                    data: "add",
                    title: this.options.messages.actions.append
                }
            }));

            this.wrapper.append(footer);
            this.footer = footer;
        },

        _createButton: function (command) {
            var template = command.template || BUTTON_TEMPLATE;
            var messages = this.options.messages.actions;
            var commandName = typeof command === STRING ? command : command.name || command.text;
            var className = defaultCommands[commandName] ? defaultCommands[commandName].className : "k-gantt-" + (commandName || "").replace(/\s/g, "");
            var options = {
                iconClass: "",
                action: "",
                text: commandName,
                className: className,
                styles: Gantt.styles.toolbar
            };

            if (!commandName && !(isPlainObject(command) && command.template)) {
                throw new Error("Custom commands should have name specified");
            }

            options = extend(true, options, defaultCommands[commandName], { text: messages[commandName] });

            if (isPlainObject(command)) {
                if (command.className && inArray(options.className, command.className.split(" ")) < 0) {
                    command.className += " " + options.className;
                }

                options = extend(true, options, command);
            }

            return kendo.template(template)(options);
        },

        _adjustDimensions: function () {
            var element = this.element;
            var ganttStyles = Gantt.styles;
            var listSelector = DOT + ganttStyles.list;
            var timelineSelector = DOT + ganttStyles.timeline;
            var splitBarSelector = DOT + ganttStyles.splitBar;
            var toolbarHeight = this.toolbar.outerHeight();
            var footerHeight = this.footer ? this.footer.outerHeight() : 0;
            var totalHeight = element.height();
            var totalWidth = element.width();
            var splitBarWidth = element.find(splitBarSelector).outerWidth();
            var treeListWidth = element.find(listSelector).outerWidth();

            element
                .children([listSelector, timelineSelector, splitBarSelector].join(","))
                .height(totalHeight - (toolbarHeight + footerHeight))
                .end()
                .children(timelineSelector)
                .width(totalWidth - (splitBarWidth + treeListWidth));
        },

        _scrollTo: function (value) {
            var view = this.timeline.view();
            var attr = kendo.attr("uid");
            var id = typeof value === "string" ? value :
                value.closest("tr" + selector()).attr(attr);
            var scrollTarget = view.content.find(selector(id));

            if (scrollTarget.length !== 0) {
                view._scrollTo(scrollTarget);
            }
        },

        _dropDowns: function () {
            var that = this;
            var actionsSelector = DOT + Gantt.styles.toolbar.actions;
            var actionMessages = this.options.messages.actions;
            var dataSource = this.dataSource;
            var timeline = this.timeline;

            var handler = function (e) {
                var type = e.type;
                var orderId;
                var task = dataSource._createNewModel();
                var selected = that.dataItem(that.select());
                var parent = dataSource.taskParent(selected);
                var firstSlot = timeline.view()._timeSlots()[0];
                var target = type === "add" ? selected : parent;

                task.set("title", "New task");

                if (target) {
                    task.set("parentId", target.get("id"));
                    task.set("start", target.get("start"));
                    task.set("end", target.get("end"));
                } else {
                    task.set("start", firstSlot.start);
                    task.set("end", firstSlot.end);
                }

                if (type !== "add") {
                    orderId = selected.get("orderId");
                    orderId = type === "insert-before" ? orderId : orderId + 1;
                }

                that._createTask(task, orderId);
            };

            if (!this.options.editable) {
                return;
            }

            this.footerDropDown = new TaskDropDown(this.footer.children(actionsSelector).eq(0), {
                messages: {
                    actions: actionMessages
                },
                direction: "up",
                animation: {
                    open: {
                        effects: "slideIn:up"
                    }
                },
                navigatable: that.options.navigatable
            });

            this.headerDropDown = new TaskDropDown(this.toolbar.children(actionsSelector).eq(0), {
                messages: {
                    actions: actionMessages
                },
                navigatable: that.options.navigatable
            });

            this.footerDropDown.bind("command", handler);
            this.headerDropDown.bind("command", handler);
        },

        _list: function () {
            var that = this;
            var navigatable = that.options.navigatable;
            var ganttStyles = Gantt.styles;
            var listWrapper = this.wrapper.find(DOT + ganttStyles.list);
            var element = listWrapper.find("> div");
            var toggleButtons = this.wrapper.find(DOT + ganttStyles.toolbar.actions + " > button");
            var options = {
                columns: this.options.columns || [],
                dataSource: this.dataSource,
                selectable: this.options.selectable,
                editable: this.options.editable,
                listWidth: listWrapper.outerWidth(),
                resourcesField: this.resources.field
            };
            var columns = options.columns;
            var column;
            var restoreFocus = function () {
                if (navigatable) {
                    that._current(that._cachedCurrent);

                    focusTable(that.list.content.find("table"), true);
                }

                delete that._cachedCurrent;
            };

            for (var i = 0; i < columns.length; i++) {
                column = columns[i];

                if (column.field === this.resources.field && typeof column.editor !== "function") {
                    column.editor = proxy(this._createResourceEditor, this);
                }
            }

            this.list = new kendo.ui.GanttList(element, options);

            this.list
                .bind("render", function () {
                    that._navigatable();
                }, true)
                .bind("edit", function (e) {
                    that._cachedCurrent = e.cell;

                    if (that.trigger("edit", { task: e.model, container: e.cell })) {
                        e.preventDefault();
                    }
                })
                .bind("cancel", function (e) {
                    if (that.trigger("cancel", { task: e.model, container: e.cell })) {
                        e.preventDefault();
                    }
                    restoreFocus();
                })
                .bind("update", function (e) {
                    that._updateTask(e.task, e.updateInfo);
                    restoreFocus();
                })
                .bind("change", function () {
                    that.trigger("change");

                    var selection = that.list.select();

                    if (selection.length) {
                        toggleButtons.removeAttr("data-action", "add");
                        that.timeline.select("[data-uid='" + selection.attr("data-uid") + "']");
                    } else {
                        toggleButtons.attr("data-action", "add");
                        that.timeline.clearSelection();
                    }
                });
        },

        _timeline: function () {
            var that = this;
            var ganttStyles = Gantt.styles;
            var options = trimOptions(extend(true, { resourcesField: this.resources.field }, this.options));
            var element = this.wrapper.find(DOT + ganttStyles.timeline + " > div");

            this.timeline = new kendo.ui.GanttTimeline(element, options);

            this.timeline
                .bind("navigate", function (e) {
                    that.toolbar
                        .find(DOT + ganttStyles.toolbar.views + " > li")
                        .removeClass(ganttStyles.selected)
                        .end()
                        .find(DOT + ganttStyles.toolbar.viewButton + "-" + e.view.replace(/\./g, "\\.").toLowerCase())
                        .addClass(ganttStyles.selected);

                    that.refresh();
                })
                .bind("moveStart", function (e) {
                    if (that.trigger("moveStart", { task: e.task })) {
                        e.preventDefault();
                    }
                })
                .bind("move", function (e) {
                    var task = e.task;
                    var start = e.start;
                    var end = new Date(start.getTime() + task.duration());

                    if (that.trigger("move", { task: task, start: start, end: end })) {
                        e.preventDefault();
                    }
                })
                .bind("moveEnd", function (e) {
                    var task = e.task;
                    var start = e.start;
                    var end = new Date(start.getTime() + task.duration());

                    if (!that.trigger("moveEnd", { task: task, start: start, end: end })) {
                        that._updateTask(that.dataSource.getByUid(task.uid), {
                            start: start,
                            end: end
                        });
                    }
                })
                .bind("resizeStart", function (e) {
                    if (that.trigger("resizeStart", { task: e.task })) {
                        e.preventDefault();
                    }
                })
                .bind("resize", function (e) {
                    if (that.trigger("resize", { task: e.task, start: e.start, end: e.end })) {
                        e.preventDefault();
                    }
                })
                .bind("resizeEnd", function (e) {
                    var task = e.task;
                    var updateInfo = {};

                    if (e.resizeStart) {
                        updateInfo.start = e.start;
                    } else {
                        updateInfo.end = e.end;
                    }

                    if (!that.trigger("resizeEnd", { task: task, start: e.start, end: e.end })) {
                        that._updateTask(that.dataSource.getByUid(task.uid), updateInfo);
                    }
                })
                .bind("percentResizeEnd", function (e) {
                    that._updateTask(that.dataSource.getByUid(e.task.uid), { percentComplete: e.percentComplete });
                })
                .bind("dependencyDragEnd", function (e) {
                    var dependency = that.dependencies._createNewModel({
                        type: e.type,
                        predecessorId: e.predecessor.id,
                        successorId: e.successor.id
                    });

                    that._createDependency(dependency);
                })
                .bind("select", function (e) {
                    that.select("[data-uid='" + e.uid + "']");
                })
                .bind("editTask", function (e) {
                    that.editTask(e.uid);
                })
                .bind("clear", function (e) {
                    that.clearSelection();
                })
                .bind("removeTask", function (e) {
                    that.removeTask(that.dataSource.getByUid(e.uid));
                })
                .bind("removeDependency", function (e) {
                    that.removeDependency(that.dependencies.getByUid(e.uid));
                });
        },

        _dataSource: function () {
            var options = this.options;
            var dataSource = options.dataSource;

            dataSource = isArray(dataSource) ? { data: dataSource} : dataSource;

            if (this.dataSource && this._refreshHandler) {
                this.dataSource
                    .unbind("change", this._refreshHandler)
                    .unbind("progress", this._progressHandler)
                    .unbind("error", this._errorHandler);
            } else {
                this._refreshHandler = proxy(this.refresh, this);
                this._progressHandler = proxy(this._requestStart, this);
                this._errorHandler = proxy(this._error, this);
            }

            this.dataSource = kendo.data.GanttDataSource.create(dataSource)
                .bind("change", this._refreshHandler)
                .bind("progress", this._progressHandler)
                .bind("error", this._errorHandler);
        },

        _dependencies: function () {
            var dependencies = this.options.dependencies || {};
            var dataSource = isArray(dependencies) ? { data: dependencies} : dependencies;

            if (this.dependencies && this._dependencyRefreshHandler) {
                this.dependencies
                    .unbind("change", this._dependencyRefreshHandler)
                    .unbind("error", this._dependencyErrorHandler);
            } else {
                this._dependencyRefreshHandler = proxy(this.refreshDependencies, this);
                this._dependencyErrorHandler = proxy(this._error, this);
            }

            this.dependencies = kendo.data.GanttDependencyDataSource.create(dataSource)
                .bind("change", this._dependencyRefreshHandler)
                .bind("error", this._dependencyErrorHandler);
        },

        _resources: function () {
            var resources = this.options.resources;
            var dataSource = resources.dataSource || {};

            this.resources = {
                field: "resources",
                dataTextField: "name",
                dataColorField: "color",
                dataFormatField: "format"
            };

            extend(this.resources, resources);

            this.resources.dataSource = kendo.data.DataSource.create(dataSource);
        },

        _assignments: function () {
            var assignments = this.options.assignments;
            var dataSource = assignments.dataSource || {};

            if (this.assignments) {
                this.assignments.dataSource
                    .unbind("change", this._assignmentsRefreshHandler);
            } else {
                this._assignmentsRefreshHandler = proxy(this.refresh, this);
            }

            this.assignments = {
                dataTaskIdField: "taskId",
                dataResourceIdField: "resourceId",
                dataValueField: "value"
            };

            extend(this.assignments, assignments);

            this.assignments.dataSource = kendo.data.DataSource.create(dataSource);

            this.assignments.dataSource.
                bind("change", this._assignmentsRefreshHandler);
        },

        _createEditor: function (command) {
            var that = this;

            var editor = this._editor = new PopupEditor(this.wrapper, extend({}, this.options, {
                target: this,
                resources: {
                    field: this.resources.field,
                    editor: proxy(this._createResourceEditor, this)
                },
                createButton: proxy(this._createPopupButton, this)
            }));

            editor
                .bind("cancel", function (e) {
                    var task = that.dataSource.getByUid(e.model.uid);

                    if (that.trigger("cancel", { container: e.container, task: task })) {
                        e.preventDefault();
                        return;
                    }

                    that.cancelTask();
                })
                .bind("edit", function (e) {
                    var task = that.dataSource.getByUid(e.model.uid);

                    if (that.trigger("edit", { container: e.container, task: task })) {
                        e.preventDefault();
                    }
                })
                .bind("save", function (e) {
                    var task = that.dataSource.getByUid(e.model.uid);

                    that.saveTask(task, e.updateInfo);
                })
                .bind("remove", function (e) {
                    that.removeTask(e.model.uid);
                });

        },

        _createResourceEditor: function (container, options) {
            var that = this;
            var model = options instanceof ObservableObject ? options : options.model;
            var id = model.get("id");
            var messages = this.options.messages;
            var resourcesField = that.resources.field;

            var editor = this._resourceEditor = new ResourceEditor(container, {
                resourcesField: resourcesField,
                data: this._wrapResourceData(id),
                model: model,
                messages: extend({}, messages.editor),
                buttons: [
                    { name: "update", text: messages.save, className: Gantt.styles.primary },
                    { name: "cancel", text: messages.cancel }
                ],
                createButton: proxy(this._createPopupButton, this),
                save: function (e) {
                    that._updateAssignments(e.model.get("id"), e.model.get(resourcesField));
                }
            });

            editor.open();
        },

        _createPopupButton: function (command) {
            var commandName = command.name || command.text;
            var options = {
                className: Gantt.styles.popup.button + " k-gantt-" + (commandName || "").replace(/\s/g, ""),
                text: commandName,
                attr: ""
            };

            if (!commandName && !(isPlainObject(command) && command.template)) {
                throw new Error("Custom commands should have name specified");
            }

            if (isPlainObject(command)) {
                if (command.className) {
                    command.className += " " + options.className;
                }

                options = extend(true, options, command);
            }

            return kendo.template(COMMAND_BUTTON_TEMPLATE)(options);
        },

        view: function (type) {
            return this.timeline.view(type);
        },

        dataItem: function (value) {
            if (!value) {
                return null;
            }

            var list = this.list;
            var element = list.content.find(value);

            return list._modelFromElement(element);
        },

        setDataSource: function (dataSource) {
            this.options.dataSource = dataSource;

            this._dataSource();

            this.list._setDataSource(this.dataSource);

            if (this.options.autoBind) {
                dataSource.fetch();
            }
        },

        setDependenciesDataSource: function (dependencies) {
            this.options.dependencies = dependencies;

            this._dependencies();

            if (this.options.autoBind) {
                dependencies.fetch();
            }
        },

        items: function () {
            return this.wrapper.children(".k-task");
        },

        _updateAssignments: function (id, resources) {
            var dataSource = this.assignments.dataSource;
            var taskId = this.assignments.dataTaskIdField;
            var resourceId = this.assignments.dataResourceIdField;
            var resourceValue = this.assignments.dataValueField;
            var hasMatch = false;
            var assignments = new Query(dataSource.view())
                .filter({
                    field: taskId,
                    operator: "eq",
                    value: id
                }).toArray();
            var assignment;
            var resource;
            var value;

            while (assignments.length) {
                assignment = assignments[0];

                for (var i = 0, length = resources.length; i < length; i++) {
                    resource = resources[i];

                    if (assignment.get(resourceId) === resource.get("id")) {
                        value = resources[i].get("value");
                        assignment.set(resourceValue, value);
                        resources.splice(i, 1);
                        hasMatch = true;
                        break;
                    }
                }

                if (!hasMatch) {
                    dataSource.remove(assignment);
                }

                hasMatch = false;

                assignments.shift();
            }

            for (var j = 0, newLength = resources.length; j < newLength; j++) {
                resource = resources[j];
                assignment = dataSource._createNewModel();
                assignment[taskId] = id;
                assignment[resourceId] = resource.get("id");
                assignment[resourceValue] = resource.get("value");
                dataSource.add(assignment);
            }

            dataSource.sync();
        },

        cancelTask: function () {
            var editor = this._editor;
            var container = editor.container;

            if (container) {
                editor.close();
            }
        },

        editTask: function (uid) {
            var task = typeof uid === "string" ? this.dataSource.getByUid(uid) : uid;

            if (!task) {
                return;
            }

            var taskCopy = this.dataSource._createNewModel(task.toJSON());
            taskCopy.uid = task.uid;

            this.cancelTask();

            this._editTask(taskCopy);
        },

        _editTask: function (task) {
            this._editor.editTask(task);
        },

        saveTask: function (task, updateInfo) {
            var editor = this._editor;
            var container = editor.container;
            var editable = editor.editable;

            if (container && editable && editable.end()) {
                this._updateTask(task, updateInfo);
            }
        },

        _updateTask: function (task, updateInfo) {
            var resourcesField = this.resources.field;

            if (!this.trigger("save", { task: task, values: updateInfo })) {
                this._preventRefresh = true;

                this.dataSource.update(task, updateInfo);

                if (updateInfo[resourcesField]) {
                    this._updateAssignments(task.get("id"), updateInfo[resourcesField]);
                }

                this._syncDataSource();
            }
        },

        removeTask: function (uid) {
            var that = this;
            var task = typeof uid === "string" ? this.dataSource.getByUid(uid) : uid;

            if (!task) {
                return;
            }

            this._taskConfirm(function (cancel) {
                if (!cancel) {
                    that._removeTask(task);
                }
            }, task);
        },

        _createTask: function (task, index) {
            if (!this.trigger("add", {
                task: task,
                dependency: null
            })) {
                var dataSource = this.dataSource;

                this._preventRefresh = true;

                if (index === undefined) {
                    dataSource.add(task);
                } else {
                    dataSource.insert(index, task);
                }

                this._scrollToUid = task.uid;

                this._syncDataSource();
            }
        },

        _createDependency: function (dependency) {
            if (!this.trigger("add", {
                task: null,
                dependency: dependency
            })) {
                this._preventDependencyRefresh = true;

                this.dependencies.add(dependency);

                this._preventDependencyRefresh = false;

                this.dependencies.sync();
            }
        },

        removeDependency: function (uid) {
            var that = this;
            var dependency = typeof uid === "string" ? this.dependencies.getByUid(uid) : uid;

            if (!dependency) {
                return;
            }

            this._dependencyConfirm(function (cancel) {
                if (!cancel) {
                    that._removeDependency(dependency);
                }
            }, dependency);
        },

        _removeTaskDependencies: function (task, dependencies) {
            this._preventDependencyRefresh = true;

            for (var i = 0, length = dependencies.length; i < length; i++) {
                this.dependencies.remove(dependencies[i]);
            }

            this._preventDependencyRefresh = false;

            this.dependencies.sync();
        },

        _removeResourceAssignments: function (task) {
            var dataSource = this.assignments.dataSource;
            var assignments = dataSource.view();
            var filter = {
                field: this.assignments.dataTaskIdField,
                operator: "eq",
                value: task.get("id")
            };

            assignments = new Query(assignments).filter(filter).toArray();

            this._preventRefresh = true;

            for (var i = 0, length = assignments.length; i < length; i++) {
                dataSource.remove(assignments[i]);
            }

            this._preventRefresh = false;

            dataSource.sync();
        },

        _removeTask: function (task) {
            var dependencies = this.dependencies.dependencies(task.id);

            if (!this.trigger("remove", {
                task: task,
                dependencies: dependencies
            })) {
                this._removeTaskDependencies(task, dependencies);
                this._removeResourceAssignments(task);

                this._preventRefresh = true;

                if (this.dataSource.remove(task)) {
                    this._syncDataSource();
                }

                this._preventRefresh = false;
            }
        },

        _removeDependency: function (dependency) {
            if (!this.trigger("remove", {
                task: null,
                dependencies: [dependency]
            })) {
                if (this.dependencies.remove(dependency)) {
                    this.dependencies.sync();
                }
            }
        },

        _taskConfirm: function (callback, task) {
            this._confirm(callback, {
                model: task,
                text: TASK_DELETE_CONFIRM,
                title: this.options.messages.deleteTaskWindowTitle
            });
        },

        _dependencyConfirm: function (callback, dependency) {
            this._confirm(callback, {
                model: dependency,
                text: DEPENDENCY_DELETE_CONFIRM,
                title: this.options.messages.deleteDependencyWindowTitle
            });
        },

        _confirm: function (callback, options) {
            var editable = this.options.editable;
            var messages;
            var buttons;

            if (editable === true || editable.confirmation !== false) {
                messages = this.options.messages;
                buttons = [
                    { name: "delete", text: messages.destroy, className: Gantt.styles.primary, click: function () { callback(); } },
                    { name: "cancel", text: messages.cancel, click: function () { callback(true); } }
                ];

                this.showDialog(extend(true, {}, options, { buttons: buttons }));
            } else {
                callback();
            }
        },

        showDialog: function (options) {
            this._editor.showDialog(options);
        },

        refresh: function (e) {
            if (this._preventRefresh || this.list.editable) {
                return;
            }

            this._progress(false);

            var dataSource = this.dataSource;
            var taskTree = dataSource.taskTree();
            var scrollToUid = this._scrollToUid;
            var current;
            var cachedUid;
            var cachedIndex = -1;

            if (this.current) {
                cachedUid = this.current.closest("tr").attr(kendo.attr("uid"));
                cachedIndex = this.current.index();
            }

            if (this.trigger("dataBinding")) {
                return;
            }

            if (this.resources.dataSource.data().length !== 0) {
                this._assignResources(taskTree);
            }

            if (this._editor) {
                this._editor.close();
            }

            this.clearSelection();
            this.list._render(taskTree);
            this.timeline._render(taskTree);
            this.timeline._renderDependencies(this.dependencies.view());

            if (scrollToUid) {
                this._scrollTo(scrollToUid);
                this.select(selector(scrollToUid));
            }

            if ((scrollToUid || cachedUid) && cachedIndex >= 0) {
                current = this.list.content
                    .find("tr" + selector((scrollToUid || cachedUid)) + " > td:eq(" + cachedIndex + ")");

                this._current(current);
            }

            this._scrollToUid = null;

            this.trigger("dataBound");
        },

        refreshDependencies: function (e) {
            if (this._preventDependencyRefresh) {
                return;
            }

            if (this.trigger("dataBinding")) {
                return;
            }

            this.timeline._renderDependencies(this.dependencies.view());

            this.trigger("dataBound");
        },

        _assignResources: function (taskTree) {
            var that = this;
            var resources = this.resources;
            var assignments = this.assignments;
            var groupAssigments = function () {
                var data = assignments.dataSource.view();
                var group = {
                    field: assignments.dataTaskIdField
                };

                data = new Query(data).group(group).toArray();

                return data;
            };
            var assigments = groupAssigments();
            var applyTaskResource = function (task, action) {
                var taskId = task.get("id");

                kendo.setter(resources.field)(task, new ObservableArray([]));

                for (var i = 0, length = assigments.length; i < length; i++) {
                    if (assigments[i].value === taskId) {
                        action(task, assigments[i].items);
                    }
                }
            };
            var wrapTask = function (task, items) {
                for (var j = 0, length = items.length; j < length; j++) {
                    var item = items[j];
                    var resource = resources.dataSource.get(item.get(assignments.dataResourceIdField));
                    var resourceValue = item.get(assignments.dataValueField);
                    var resourcedId = item.get(assignments.dataResourceIdField);
                    var valueFormat = resource.get(resources.dataFormatField) || PERCENTAGE_FORMAT;
                    var formatedValue = kendo.toString(resourceValue, valueFormat);

                    task[resources.field].push(new ObservableObject({
                        id: resourcedId,
                        name: resource.get(resources.dataTextField),
                        color: resource.get(resources.dataColorField),
                        value: resourceValue,
                        formatedValue: formatedValue
                    }));
                }
            };

            for (var i = 0, length = taskTree.length; i < length; i++) {
                applyTaskResource(taskTree[i], wrapTask);
            }
        },

        _wrapResourceData: function (id) {
            var that = this;
            var result = [];
            var resource;
            var resources = this.resources.dataSource.view();
            var assignments = this.assignments.dataSource.view();
            var taskAssignments = new Query(assignments).filter({
                field: that.assignments.dataTaskIdField,
                operator: "eq",
                value: id
            }).toArray();
            var valuePerResource = function (id) {
                var resourceValue = null;

                new Query(taskAssignments).filter({
                    field: that.assignments.dataResourceIdField,
                    operator: "eq",
                    value: id
                }).select(function (assignment) {
                    resourceValue += assignment.get(that.assignments.dataValueField);
                });

                return resourceValue;
            };

            for (var i = 0, length = resources.length; i < length; i++) {
                resource = resources[i];
                result.push({
                    id: resource.get("id"),
                    name: resource.get(that.resources.dataTextField),
                    format: resource.get(that.resources.dataFormatField) || PERCENTAGE_FORMAT,
                    value: valuePerResource(resource.id)
                });
            }

            return result;
        },

        _syncDataSource: function () {
            this._preventRefresh = false;
            this._requestStart();
            this.dataSource.sync();
        },

        _requestStart: function () {
            this._progress(true);
        },

        _error: function () {
            this._progress(false);
        },

        _progress: function (toggle) {
            kendo.ui.progress(this.element, toggle);
        },

        _resizable: function () {
            var wrapper = this.wrapper;
            var ganttStyles = Gantt.styles;
            var contentSelector = DOT + ganttStyles.gridContent;
            var treeListWrapper = wrapper.find(DOT + ganttStyles.list);
            var timelineWrapper = wrapper.find(DOT + ganttStyles.timeline);
            var treeListWidth;
            var timelineWidth;
            var timelineScroll;

            this._resizeDraggable = wrapper
                .find(DOT + ganttStyles.splitBar)
                .height(treeListWrapper.height())
                .hover(function (e) {
                    $(this).addClass(ganttStyles.splitBarHover);
                }, function (e) {
                    $(this).removeClass(ganttStyles.splitBarHover);
                })
                .end()
                .kendoResizable({
                    orientation: "horizontal",
                    handle: DOT + ganttStyles.splitBar,
                    "start": function (e) {
                        treeListWidth = treeListWrapper.width();
                        timelineWidth = timelineWrapper.width();
                        timelineScroll = timelineWrapper.find(contentSelector).scrollLeft();
                    },
                    "resize": function (e) {
                        var delta = e.x.initialDelta;

                        if (treeListWidth + delta < 0 || timelineWidth - delta < 0) {
                            return;
                        }

                        treeListWrapper.width(treeListWidth + delta);
                        timelineWrapper.width(timelineWidth - delta);
                        timelineWrapper.find(contentSelector).scrollLeft(timelineScroll + delta);
                    }
                }).data("kendoResizable");
        },

        _scrollable: function () {
            var ganttStyles = Gantt.styles;
            var contentSelector = DOT + ganttStyles.gridContent;
            var headerSelector = DOT + ganttStyles.gridHeaderWrap;
            var timelineWrapper = this.timeline.element;
            var treeListWrapper = this.list.element;

            timelineWrapper.find(contentSelector).on("scroll", function (e) {
                timelineWrapper.find(headerSelector).scrollLeft(this.scrollLeft);
                treeListWrapper.find(contentSelector).scrollTop(this.scrollTop);
            });

            treeListWrapper.find(contentSelector)
                .on("scroll", function (e) {
                    treeListWrapper.find(headerSelector).scrollLeft(this.scrollLeft);
                })
                .on("DOMMouseScroll" + NS + " mousewheel" + NS, function (e) {
                    var content = timelineWrapper.find(contentSelector);
                    var scrollTop = content.scrollTop();
                    var delta = kendo.wheelDeltaY(e);

                    if (delta) {
                        e.preventDefault();
                        //In Firefox DOMMouseScroll event cannot be canceled
                        $(e.currentTarget).one("wheel" + NS, false);

                        content.scrollTop(scrollTop + (-delta));
                    }
                });
        },

        _navigatable: function () {
            var that = this;
            var navigatable = this.options.navigatable;
            var editable = this.options.editable;
            var headerTable = this.list.header.find("table");
            var contentTable = this.list.content.find("table");
            var ganttStyles = Gantt.styles;
            var timelineContent = this.timeline.element.find(DOT + ganttStyles.gridContent);
            var tables = headerTable.add(contentTable);
            var attr = selector();
            var cellIndex;
            var expandState = {
                collapse: false,
                expand: true
            };

            var scroll = function (reverse) {
                var width = that.timeline.view()._timeSlots()[0].offsetWidth;
                timelineContent.scrollLeft(timelineContent.scrollLeft() + (reverse ? -width : width));
            };
            var moveVertical = function (method) {
                var parent = that.current.parent("tr" + selector());
                var index = that.current.index();
                var subling = parent[method]();

                if (that.select().length !== 0) {
                    that.clearSelection();
                }

                if (subling.length !== 0) {
                    that._current(subling.children("td:eq(" + index + ")"));
                    that._scrollTo(that.current);
                } else {
                    if (that.current.is("td") && method == "prev") {
                        focusTable(headerTable);
                    } else if (that.current.is("th") && method == "next") {
                        focusTable(contentTable);
                    }
                }
            };
            var moveHorizontal = function (method) {
                var subling = that.current[method]();

                if (subling.length !== 0) {
                    that._current(subling);
                    cellIndex = that.current.index();
                }
            };
            var toggleExpandedState = function (value) {
                var model = that.dataItem(that.current);

                if (model.summary && model.expanded !== value) {
                    model.set("expanded", value);
                }
            };
            var deleteAction = function () {
                if (!that.options.editable || that.list.editable) {
                    return;
                }

                var selectedTask = that.select();
                var uid = kendo.attr("uid");

                if (selectedTask.length) {
                    that.removeTask(selectedTask.attr(uid));
                }
            };

            $(this.wrapper)
                .on("mousedown" + NS, "tr" + attr + ", div" + attr + ":not(" + DOT + ganttStyles.line + ")", function (e) {
                    var currentTarget = $(e.currentTarget);
                    var isInput = $(e.target).is(":button,a,:input,a>.k-icon,textarea,span.k-icon,span.k-link,.k-input,.k-multiselect-wrap");
                    var current;

                    if (e.ctrlKey) {
                        return;
                    }

                    if (navigatable) {
                        if (currentTarget.is("tr")) {
                            current = $(e.target).closest("td");
                        } else {
                            current = that.list
                                .content.find("tr" + selector(currentTarget.attr(kendo.attr("uid"))) + " > td:first");
                        }

                        that._current(current);
                    }

                    if ((navigatable || editable) && !isInput) {
                        setTimeout(function () {
                            focusTable(that.list.content.find("table"), true);
                        }, 2);
                    }
                });

            if (navigatable !== true) {
                contentTable
                    .on("keydown" + NS, function (e) {
                        if (e.keyCode == keys.DELETE) {
                            deleteAction();
                        }
                    });

                return;
            }

            tables
                .on("focus" + NS, function (e) {
                    var selector = this === contentTable.get(0) ? "td" : "th";
                    var table = $(this);
                    var selection = that.select();
                    var current = that.current || $((selection.length ? selection : this))
                        .find(selector + ":eq(" + (cellIndex || 0) + ")");

                    that._current(current);
                })
                .on("blur" + NS, function () {
                    that._current();

                    if (this == headerTable) {
                        $(this).attr(TABINDEX, -1);
                    }
                })
                .on("keydown" + NS, function (e) {
                    var key = e.keyCode;
                    var isCell;

                    if (!that.current) {
                        return;
                    }

                    isCell = that.current.is("td");

                    switch (key) {
                        case keys.RIGHT:
                            e.preventDefault();
                            if (e.altKey) {
                                scroll();
                            } else if (e.ctrlKey) {
                                toggleExpandedState(expandState.expand);
                            } else {
                                moveHorizontal("next");
                            }
                            break;
                        case keys.LEFT:
                            e.preventDefault();
                            if (e.altKey) {
                                scroll(true);
                            } else if (e.ctrlKey) {
                                toggleExpandedState(expandState.collapse);
                            } else {
                                moveHorizontal("prev");
                            }
                            break;
                        case keys.UP:
                            e.preventDefault();
                            moveVertical("prev");
                            break;
                        case keys.DOWN:
                            e.preventDefault();
                            moveVertical("next");
                            break;
                        case keys.SPACEBAR:
                            e.preventDefault();
                            if (isCell) {
                                that.select(that.current.closest("tr"));
                            }
                            break;
                        case keys.ENTER:
                            e.preventDefault();
                            if (isCell) {
                                if (that.options.editable) {
                                    that._cachedCurrent = that.current;
                                    that.list._startEditHandler(that.current);
                                    /* Stop the event propagation so that the list widget won't close its editor immediately */
                                    e.stopPropagation();
                                }
                            } else {
                                /* Sort */
                                that.current
                                    .children("a.k-link")
                                    .click();
                            }
                            break;
                        case keys.ESC:
                            e.stopPropagation();
                            break;
                        case keys.DELETE:
                            if (isCell) {
                                deleteAction();
                            }
                            break;
                        default:
                            if (key >= 49 && key <= 57) {
                                that.view(that.timeline._viewByIndex(key - 49));
                            }
                            break;
                    }
                });
        },

        _current: function (element) {
            var ganttStyles = Gantt.styles;
            var activeElement;

            if (this.current && this.current.length) {
                this.current
                    .removeClass(ganttStyles.focused)
                    .removeAttr("id");
            }

            if (element && element.length) {
                this.current = element
                    .addClass(ganttStyles.focused)
                    .attr("id", ACTIVE_CELL);

                activeElement = $(kendo._activeElement());

                if (activeElement.is("table") && this.wrapper.find(activeElement).length > 0) {
                    activeElement
                        .removeAttr(ARIA_DESCENDANT)
                        .attr(ARIA_DESCENDANT, ACTIVE_CELL);
                }
            } else {
                this.current = null;
            }
        },

        _dataBind: function () {
            var that = this;

            if (that.options.autoBind) {
                this._preventRefresh = true;
                this._preventDependencyRefresh = true;

                var promises = $.map([
                    this.dataSource,
                    this.dependencies,
                    this.resources.dataSource,
                    this.assignments.dataSource
                ],
                function (dataSource) {
                    return dataSource.fetch();
                });

                $.when.apply(null, promises)
                    .done(function () {
                        that._preventRefresh = false;
                        that._preventDependencyRefresh = false;
                        that.refresh();
                    });
            }
        },

        _resize: function () {
            this._adjustDimensions();
            this.timeline.view()._adjustHeight();
            this.list._adjustHeight();
        }
    });

    if (kendo.PDFMixin) {
        kendo.PDFMixin.extend(Gantt.prototype);
    }

    kendo.ui.plugin(Gantt);

    extend(true, Gantt, { styles: ganttStyles });

})(window.kendo.jQuery);





(function ($, undefined) {
    var data = kendo.data;
    var extend = $.extend;
    var kendoDom = kendo.dom;
    var kendoDomElement = kendoDom.element;
    var kendoTextElement = kendoDom.text;
    var kendoHtmlElement = kendoDom.html;
    var ui = kendo.ui;
    var DataBoundWidget = ui.DataBoundWidget;
    var DataSource = data.DataSource;
    var ObservableArray = data.ObservableArray;
    var Query = data.Query;
    var Model = data.Model;
    var proxy = $.proxy;
    var map = $.map;
    var grep = $.grep;
    var STRING = "string";
    var CHANGE = "change";
    var ERROR = "error";
    var PROGRESS = "progress";
    var DOT = ".";
    var NS = ".kendoTreeList";
    var CLICK = "click";
    var MOUSEDOWN = "mousedown";
    var EDIT = "edit";
    var SAVE = "save";
    var EXPAND = "expand";
    var COLLAPSE = "collapse";
    var REMOVE = "remove";
    var DATABINDING = "dataBinding";
    var DATABOUND = "dataBound";
    var CANCEL = "cancel";
    var FILTERMENUINIT = "filterMenuInit";

    var classNames = {
        wrapper: "k-treelist k-grid k-widget",
        header: "k-header",
        button: "k-button",
        alt: "k-alt",
        editCell: "k-edit-cell",
        group: "k-treelist-group",
        gridToolbar: "k-grid-toolbar",
        gridHeader: "k-grid-header",
        gridHeaderWrap: "k-grid-header-wrap",
        gridContent: "k-grid-content",
        gridContentWrap: "k-grid-content",
        gridFilter: "k-grid-filter",
        footerTemplate: "k-footer-template",
        loading: "k-loading",
        refresh: "k-i-refresh",
        retry: "k-request-retry",
        selected: "k-state-selected",
        status: "k-status",
        link: "k-link",
        withIcon: "k-with-icon",
        filterable: "k-filterable",
        icon: "k-icon",
        iconFilter: "k-filter",
        iconCollapse: "k-i-collapse",
        iconExpand: "k-i-expand",
        iconHidden: "k-i-none",
        iconPlaceHolder: "k-icon k-i-none",
        input: "k-input",
        dropPositions: "k-insert-top k-insert-bottom k-add k-insert-middle",
        dropTop: "k-insert-top",
        dropBottom: "k-insert-bottom",
        dropAdd: "k-add",
        dropMiddle: "k-insert-middle",
        dropDenied: "k-denied",
        dragStatus: "k-drag-status",
        dragClue: "k-drag-clue",
        dragClueText: "k-clue-text"
    };

    var defaultCommands = {
        create: {
            imageClass: "k-add",
            className: "k-grid-add",
            methodName: "addRow"
        },
        createchild: {
            imageClass: "k-add",
            className: "k-grid-add",
            methodName: "addRow"
        },
        destroy: {
            imageClass: "k-delete",
            className: "k-grid-delete",
            methodName: "removeRow"
        },
        edit: {
            imageClass: "k-edit",
            className: "k-grid-edit",
            methodName: "editRow"
        },
        update: {
            imageClass: "k-update",
            className: "k-primary k-grid-update",
            methodName: "saveRow"
        },
        canceledit: {
            imageClass: "k-cancel",
            className: "k-grid-cancel",
            methodName: "_cancelEdit"
        },
        excel: {
            imageClass: "k-i-excel",
            className: "k-grid-excel",
            methodName: "saveAsExcel"
        },
        pdf: {
            imageClass: "k-i-pdf",
            className: "k-grid-pdf",
            methodName: "saveAsPDF"
        }
    };

    var TreeListModel = Model.define({
        id: "id",

        fields: {
            id: { type: "number" },
            parentId: { type: "number", nullable: true }
        },

        init: function (value) {
            Model.fn.init.call(this, value);

            this._loaded = false;
        },

        loaded: function (value) {
            if (value !== undefined) {
                this._loaded = value;
            } else {
                return this._loaded;
            }
        },

        shouldSerialize: function (field) {
            return Model.fn.shouldSerialize.call(this, field) && field !== "_loaded" && field != "_error" && field != "_edit";
        }
    });

    var TreeListDataSource = DataSource.extend({
        init: function (options) {
            DataSource.fn.init.call(this, extend(true, {}, {
                schema: {
                    modelBase: TreeListModel,
                    model: TreeListModel
                }
            }, options));
        },

        _readData: function (newData) {
            var result = [];
            var data = this.data();
            var i, length;

            for (i = 0, length = data.length; i < length; i++) {
                result.push(data[i]);
            }

            return result.concat(DataSource.fn._readData.call(this, newData));
        },

        _readAggregates: function (data) {
            var result = extend(this._aggregateResult, this.reader.aggregates(data));
            if ("" in result) {
                result[this._defaultParentId()] = result[""];
                delete result[""];
            }

            return result;
        },

        remove: function (root) {
            var items = this._subtree(this._childrenMap(this.data()), root.id);

            this._removeItems(items);

            DataSource.fn.remove.call(this, root);
        },

        _filterCallback: function (query) {
            var result = [];
            var data = query.toArray();
            var map = {};
            var i, parent, item;

            for (i = 0; i < data.length; i++) {
                item = data[i];

                while (item) {
                    map[item.id] = true;

                    if (!map[item.parentId]) {
                        map[item.parentId] = true;
                        item = this.parentNode(item);

                        if (item) {
                            result.push(item);
                        }
                    } else {
                        break;
                    }
                }
            }

            return new Query(data.concat(result));
        },

        _subtree: function (map, id) {
            var result = map[id] || [];
            var defaultParentId = this._defaultParentId();

            for (var i = 0, len = result.length; i < len; i++) {
                if (result[i].id !== defaultParentId) {
                    result = result.concat(this._subtree(map, result[i].id));
                }
            }

            return result;
        },

        // builds hash id -> children
        _childrenMap: function (data) {
            var map = {};
            var i, item, id, parentId;

            data = this._observeView(data);

            for (i = 0; i < data.length; i++) {
                item = data[i];
                id = item.id;
                parentId = item.parentId;

                map[id] = map[id] || [];
                map[parentId] = map[parentId] || [];
                map[parentId].push(item);
            }

            return map;
        },

        _calculateAggregates: function (data, options) {
            options = options || {};

            var result = {};
            var item, subtree, i, id, parentId;
            var filter = options.filter;

            if (filter) {
                data = Query.process(data, {
                    filter: filter,
                    filterCallback: proxy(this._filterCallback, this)
                }).data;
            }

            var map = this._childrenMap(data);

            // calculate aggregates for each subtree
            result[this._defaultParentId()] = new Query(this._subtree(map, this._defaultParentId())).aggregate(options.aggregate);

            for (i = 0; i < data.length; i++) {
                item = data[i];
                subtree = this._subtree(map, item.id);

                result[item.id] = new Query(subtree).aggregate(options.aggregate);
            }

            return result;
        },

        _queryProcess: function (data, options) {
            options = options || {};

            options.filterCallback = proxy(this._filterCallback, this);

            var defaultParentId = this._defaultParentId();
            var result = Query.process(data, options);
            var map = this._childrenMap(result.data);
            var hasChildren, i, item, children;

            data = map[defaultParentId] || [];

            for (i = 0; i < data.length; i++) {
                item = data[i];

                if (item.id === defaultParentId) {
                    continue;
                }

                children = map[item.id];
                hasChildren = !!(children && children.length);

                if (!item.loaded()) {
                    item.loaded(hasChildren);
                }

                if (item.loaded() || item.hasChildren !== true) {
                    item.hasChildren = hasChildren;
                }

                if (hasChildren) {
                    data.splice.apply(data, [i + 1, 0].concat(children));
                }
            }

            result.data = data;

            return result;
        },

        _queueRequest: function (options, callback) {
            // allow simultaneous requests (loading multiple items at the same time)
            callback.call(this);
        },

        _modelLoaded: function (id) {
            var model = this.get(id);
            model.loaded(true);
            model.hasChildren = this.childNodes(model).length > 0;
        },

        _modelError: function (id, e) {
            this.get(id)._error = e;
        },

        read: function (data) {
            if (!data || !data.id) {
                this._data = this._observe([]);
            }

            return DataSource.fn.read.call(this, data);
        },

        load: function (model) {
            var method = "_query";

            if (!model.loaded() && model.hasChildren) {
                method = "read";
            }

            return this[method]({ id: model.id }).then(
                proxy(this._modelLoaded, this, model.id),
                proxy(this._modelError, this, model.id)
            );
        },

        _byParentId: function (id, defaultId) {
            var result = [];
            var view = this.view();
            var current;

            if (id === defaultId) {
                return [];
            }

            for (var i = 0; i < view.length; i++) {
                current = view.at(i);

                if (current.parentId == id) {
                    result.push(current);
                }
            }

            return result;
        },

        _defaultParentId: function () {
            return this.reader.model.fn.defaults.parentId;
        },

        childNodes: function (model) {
            return this._byParentId(model.id, this._defaultParentId());
        },

        rootNodes: function () {
            return this._byParentId(this._defaultParentId());
        },

        parentNode: function (model) {
            return this.get(model.parentId);
        },

        level: function (model) {
            var result = -1;

            if (!(model instanceof TreeListModel)) {
                model = this.get(model);
            }

            do {
                model = this.parentNode(model);
                result++;
            } while (model);

            return result;
        },

        filter: function (value) {
            var baseFilter = DataSource.fn.filter;

            if (value === undefined) {
                return baseFilter.call(this, value);
            }

            baseFilter.call(this, value);
        }
    });

    TreeListDataSource.create = function (options) {
        if ($.isArray(options)) {
            options = { data: options };
        } else if (options instanceof ObservableArray) {
            options = { data: options.toJSON() };
        }

        return options instanceof TreeListDataSource ? options : new TreeListDataSource(options);
    };

    function createPlaceholders(options) {
        var spans = [];
        var className = options.className;

        for (var i = 0, level = options.level; i < level; i++) {
            spans.push(kendoDomElement("span", { className: className }));
        }

        return spans;
    }

    var Editor = kendo.Observable.extend({
        init: function (element, options) {
            kendo.Observable.fn.init.call(this);

            options = this.options = extend(true, {}, this.options, options);

            this.element = element;

            this.bind(this.events, options);

            this.model = this.options.model;

            this.fields = this._fields(this.options.columns);

            this._initContainer();

            this.createEditable();
        },

        events: [],

        _initContainer: function () {
            this.wrapper = this.element;
        },

        createEditable: function () {
            var options = this.options;

            this.editable = new ui.Editable(this.wrapper, {
                fields: this.fields,
                target: options.target,
                clearContainer: options.clearContainer,
                model: this.model
            });
        },

        _isEditable: function (column) {
            return column.field && this.model.editable(column.field);
        },

        _fields: function (columns) {
            var fields = [];
            var idx, length, column;

            for (idx = 0, length = columns.length; idx < length; idx++) {
                column = columns[idx];

                if (this._isEditable(column)) {
                    fields.push({
                        field: column.field,
                        format: column.format,
                        editor: column.editor
                    });
                }
            }

            return fields;
        },

        end: function () {
            return this.editable.end();
        },

        close: function () {
            this.destroy();
        },

        destroy: function () {
            this.editable.destroy();
            this.editable.element.find("[" + kendo.attr("container-for") + "]").empty();
            this.model = this.wrapper = this.element = this.columns = this.editable = null;
        }
    });

    var PopupEditor = Editor.extend({
        init: function (element, options) {
            Editor.fn.init.call(this, element, options);

            this._attachHandlers();

            this.open();
        },

        events: [
            CANCEL,
            SAVE
        ],

        options: {
            window: {
                modal: true,
                resizable: false,
                draggable: true,
                title: "Edit",
                visible: false
            }
        },

        _initContainer: function () {
            var options = this.options;
            var formContent = [];

            this.wrapper = $('<div class="k-popup-edit-form"/>')
                .attr(kendo.attr("uid"), this.model.uid)
                .append('<div class="k-edit-form-container"/>');

            if (options.template) {
                this._appendTemplate(formContent);
                this.fields = [];
            } else {
                this._appendFields(formContent);
            }
            this._appendButtons(formContent);

            new kendoDom.Tree(this.wrapper.children()[0]).render(formContent);

            this.wrapper.appendTo(options.appendTo);

            this.window = new ui.Window(this.wrapper, options.window);
        },

        _appendTemplate: function (form) {
            var template = this.options.template;

            if (typeof template === STRING) {
                template = window.unescape(template);
            }

            template = kendo.template(template)(this.model);

            form.push(kendoHtmlElement(template));
        },

        _appendFields: function (form) {
            var idx, length, column;
            var model = this.model;
            var columns = this.options.columns;

            for (idx = 0, length = columns.length; idx < length; idx++) {
                column = columns[idx];

                if (column.command) {
                    continue;
                }

                form.push(kendoHtmlElement('<div class="k-edit-label"><label for="' + column.field + '">' + (column.title || column.field || "") + '</label></div>'));

                if (this._isEditable(column)) {
                    form.push(kendoHtmlElement('<div ' + kendo.attr("container-for") + '="' + column.field +
                                '" class="k-edit-field"></div>'));
                } else {
                    form.push(kendoDomElement("div", {
                        "class": "k-edit-field"
                    },
                        [this.options.fieldRenderer(column, this.model)]));
                }
            }
        },

        _appendButtons: function (form) {
            form.push(kendoDomElement("div", {
                "class": "k-edit-buttons k-state-default"
            }, this.options.commandRenderer()));
        },

        _attachHandlers: function () {
            var closeHandler = this._cancelProxy = proxy(this._cancel, this);
            this.wrapper.on(CLICK + NS, ".k-grid-cancel", this._cancelProxy);

            this._saveProxy = proxy(this._save, this);
            this.wrapper.on(CLICK + NS, ".k-grid-update", this._saveProxy);

            this.window.bind("close", function (e) {
                if (e.userTriggered) {
                    closeHandler(e);
                }
            });
        },

        _dettachHandlers: function () {
            this._cancelProxy = null;
            this._saveProxy = null;
            this.wrapper.off(NS);
        },

        _cancel: function (e) {
            this.trigger(CANCEL, e);
        },

        _save: function (e) {
            this.trigger(SAVE);
        },

        open: function () {
            this.window.center().open();
        },

        close: function () {
            this.window.bind("deactivate", proxy(this.destroy, this)).close();
        },

        destroy: function () {
            this.window.destroy();
            this.window = null;
            this._dettachHandlers();

            Editor.fn.destroy.call(this);
        }
    });

    var TreeList = DataBoundWidget.extend({
        init: function (element, options) {
            DataBoundWidget.fn.init.call(this, element, options);

            this._dataSource(this.options.dataSource);

            this._columns();
            this._layout();
            this._sortable();
            this._filterable();
            this._selectable();
            this._attachEvents();
            this._toolbar();
            this._scrollable();

            if (this.options.autoBind) {
                this.dataSource.fetch();
            }

            this._adjustHeight();

            kendo.notify(this);
        },

        _scrollable: function () {
            if (this.options.scrollable) {
                var scrollables = this.header.closest(".k-grid-header-wrap");
                this.content.closest(".k-grid-content").bind("scroll" + NS, function () {
                    scrollables.scrollLeft(this.scrollLeft);
                });


                var touchScroller = kendo.touchScroller(this.content.closest("div"));

                if (touchScroller && touchScroller.movable) {
                    this._touchScroller = touchScroller;
                }
            }
        },

        _progress: function () {
            var messages = this.options.messages;

            if (!this.content.find("tr").length) {
                this._showStatus(
                    kendo.template(
                        "<span class='#= className #' /> #: messages.loading #"
                    )({
                        className: classNames.icon + " " + classNames.loading,
                        messages: messages
                    })
                );
            }
        },

        _error: function (e) {
            if (!this.dataSource.rootNodes().length) {
                this._render({ error: e });
            }
        },

        refresh: function (e) {
            e = e || {};

            if (e.action == "itemchange" && this.editor) {
                return;
            }

            if (this.trigger(DATABINDING)) {
                return;
            }

            this._angularItems("cleanup");
            this._angularFooters("cleanup");

            this._cancelEditor();

            this._render();

            this._adjustHeight();

            this._angularItems("compile");
            this._angularFooters("compile");

            this.trigger(DATABOUND);
        },

        _angularFooters: function (command) {
            var i, footer, aggregates;
            var allAggregates = this.dataSource.aggregates();
            var footerRows = this.content.find("tr").filter(function () {
                return $(this).hasClass(classNames.footerTemplate);
            });

            for (i = 0; i < footerRows.length; i++) {
                footer = footerRows.eq(i);
                aggregates = allAggregates[footer.attr("data-parentId")];

                this._angularFooter(command, footer.find("td").get(), aggregates);
            }
        },

        _angularFooter: function (command, cells, aggregates) {
            var columns = this.columns;
            this.angular(command, function () {
                return {
                    elements: cells,
                    data: map(columns, function (col, i) {
                        return {
                            column: col,
                            aggregate: aggregates && aggregates[col.field]
                        };
                    })
                };
            });
        },

        items: function () {
            return this.content.find("tr").filter(function () {
                return !$(this).hasClass(classNames.footerTemplate);
            });
        },

        _showStatus: function (message) {
            var status = this.element.find(".k-status");

            if (!status.length) {
                status = $("<div class='k-status' />").appendTo(this.element);
            }

            this._contentTree.render([]);

            this.content.closest(DOT + classNames.gridContent).hide();

            status.html(message);
        },

        _hideStatus: function () {
            this.element.find(".k-status").remove();
            this.content.closest(DOT + classNames.gridContent).show();
        },

        _adjustHeight: function () {
            var element = this.element;
            var contentWrap = element.find(DOT + classNames.gridContentWrap);
            var header = element.find(DOT + classNames.gridHeader);
            var toolbar = element.find(DOT + classNames.gridToolbar);

            element.height(this.options.height);

            // identical code found in grid & scheduler :(
            var isHeightSet = function (el) {
                var initialHeight, newHeight;
                if (el[0].style.height) {
                    return true;
                } else {
                    initialHeight = el.height();
                }

                el.height("auto");
                newHeight = el.height();
                el.height("");

                return (initialHeight != newHeight);
            };

            if (isHeightSet(element)) {
                contentWrap.height(element.height() - header.outerHeight() - toolbar.outerHeight());
            }
        },

        destroy: function () {
            DataBoundWidget.fn.destroy.call(this);

            var dataSource = this.dataSource;

            dataSource.unbind(CHANGE, this._refreshHandler);
            dataSource.unbind(ERROR, this._errorHandler);
            dataSource.unbind(PROGRESS, this._progressHandler);

            this._destroyEditor();

            this.element.off(NS);

            if (this._touchScroller) {
                this._touchScroller.destroy();
            }

            this._refreshHandler = this._errorHandler = this._progressHandler = null;
            this.header = this.content = this.element = null;
            this._statusTree = this._headerTree = this._contentTree = null;
        },

        options: {
            name: "TreeList",
            columns: [],
            autoBind: true,
            scrollable: true,
            selectable: false,
            sortable: false,
            toolbar: null,
            height: null,
            messages: {
                noRows: "No records to display",
                loading: "Loading...",
                requestFailed: "Request failed.",
                retry: "Retry",
                commands: {
                    edit: "Edit",
                    update: "Update",
                    canceledit: "Cancel",
                    create: "Add new record",
                    createchild: "Add child record",
                    destroy: "Delete",
                    excel: "Export to Excel",
                    pdf: "Export to PDF"
                }
            },
            excel: {
                hierarchy: true
            },
            filterable: false,
            editable: false
        },

        events: [
            CHANGE,
            EDIT,
            SAVE,
            REMOVE,
            EXPAND,
            COLLAPSE,
            DATABINDING,
            DATABOUND,
            CANCEL,
            FILTERMENUINIT
        ],

        _toggle: function (model, expand) {
            var loaded = model.loaded();

            // reset error state
            if (model._error) {
                model.expanded = false;
                model._error = undefined;
            }

            // do not load items that are currently loading
            if (!loaded && model.expanded) {
                return;
            }

            // toggle expanded state
            if (typeof expand == "undefined") {
                expand = !model.expanded;
            }

            model.expanded = expand;

            if (!loaded) {
                this.dataSource.load(model)
                    .always(proxy(this.refresh, this));
            }

            this.refresh();
        },

        expand: function (row) {
            this._toggle(this.dataItem(row), true);
        },

        collapse: function (row) {
            this._toggle(this.dataItem(row), false);
        },

        _toggleChildren: function (e) {
            var icon = $(e.currentTarget);
            var model = this.dataItem(icon);
            var event = !model.expanded ? EXPAND : COLLAPSE;

            if (!this.trigger(event, { model: model })) {
                this._toggle(model);
            }

            e.preventDefault();
        },

        _attachEvents: function () {
            var icons = DOT + classNames.iconCollapse +
                ", ." + classNames.iconExpand +
                ", ." + classNames.refresh;
            var retryButton = DOT + classNames.retry;
            var dataSource = this.dataSource;

            this.element
                .on(MOUSEDOWN + NS, icons, proxy(this._toggleChildren, this))
                .on(CLICK + NS, retryButton, proxy(dataSource.fetch, dataSource))
                .on(CLICK + NS, ".k-button[data-command]", proxy(this._commandClick, this));
        },

        _commandByName: function (name) {
            var columns = this.columns;
            var i, j, commands;

            name = name.toLowerCase();

            if (defaultCommands[name]) {
                return defaultCommands[name];
            }

            // command not found in defaultCommands, must be custom
            for (i = 0; i < columns.length; i++) {
                commands = columns[i].command;
                if (commands) {
                    for (j = 0; j < commands.length; j++) {
                        if (commands[j].name.toLowerCase() == name) {
                            return commands[j];
                        }
                    }
                }
            }
        },

        _commandClick: function (e) {
            var button = $(e.currentTarget);
            var commandName = button.attr("data-command");
            var command = this._commandByName(commandName);
            var row = button.closest("tr");

            row = row.length ? row : undefined;

            if (command) {
                if (command.methodName) {
                    this[command.methodName](row);
                } else if (command.click) {
                    command.click.call(this, e);
                }
            }
        },

        _columns: function () {
            var columns = this.options.columns || [];

            this.columns = map(columns, function (column) {
                column = (typeof column === "string") ? { field: column} : column;

                return extend({ encoded: true }, column);
            });

            var expandableColumns = grep(this.columns, function (c) {
                return c.expandable;
            });

            if (this.columns.length && !expandableColumns.length) {
                this.columns[0].expandable = true;
            }

            this._columnTemplates();
            this._columnAttributes();
        },

        _columnTemplates: function () {
            var idx, length, column;
            var columns = this.columns;

            for (idx = 0, length = columns.length; idx < length; idx++) {
                column = columns[idx];
                if (column.template) {
                    column.template = kendo.template(column.template);
                }

                if (column.headerTemplate) {
                    column.headerTemplate = kendo.template(column.headerTemplate);
                }

                if (column.footerTemplate) {
                    column.footerTemplate = kendo.template(column.footerTemplate);
                }
            }
        },

        _columnAttributes: function () {
            // column style attribute is string, kendo.dom expects object
            var idx, length, column;
            var columns = this.columns;
            var attr, properties, i, declaration;

            for (idx = 0, length = columns.length; idx < length; idx++) {
                attr = columns[idx].attributes;

                if (attr && attr.style) {
                    properties = attr.style.split(";");
                    attr.style = {};

                    for (i = 0; i < properties.length; i++) {
                        declaration = properties[i].split(":");
                        attr.style[declaration[0]] = declaration[1];
                    }
                }
            }
        },

        _layout: function () {
            var element = this.element;
            var colgroup = this._colgroup();
            var layout = "";

            this.wrapper = element.addClass(classNames.wrapper);

            layout =
                "<div class='#= gridHeader #' style=\"padding-right: " + kendo.support.scrollbar() + "px;\">" +
                    "<div class='#= gridHeaderWrap #'>" +
                        "<table role='grid'>" +
                            colgroup +
                            "<thead role='rowgroup' />" +
                        "</table>" +
                    "</div>" +
                "</div>" +
                "<div class='#= gridContentWrap #'>" +
                    "<table role='treegrid' tabindex='0'>" +
                        colgroup +
                        "<tbody />" +
                    "</table>" +
                "</div>";

            if (!this.options.scrollable) {
                layout =
                    "<table role='treegrid' tabindex='0'>" +
                        colgroup +
                        "<thead class='#= gridHeader #' role='rowgroup' />" +
                        "<tbody />" +
                    "</table>";
            }

            if (this.options.toolbar) {
                layout = "<div class='#= header # #= gridToolbar #' />" + layout;
            }

            element.append(
                kendo.template(layout)(classNames) +
                "<div class='k-status' />"
            );

            this.toolbar = element.find(DOT + classNames.gridToolbar);

            var header = this.header = element.find(DOT + classNames.gridHeader).find("thead").addBack().filter("thead");
            this._headerTree = new kendoDom.Tree(this.header[0]);
            this._headerTree.render([kendoDomElement("tr", { "role": "row" }, this._ths())]);

            var columns = this.columns;

            this.angular("compile", function () {
                return {
                    elements: header.find("th.k-header").get(),
                    data: map(columns, function (col) { return { column: col }; })
                };
            });

            this.content = element.find(DOT + classNames.gridContentWrap).find("tbody");

            if (!this.content.length) {
                this.content = element.find("tbody");
            }

            this._contentTree = new kendoDom.Tree(this.content[0]);

            this._statusTree = new kendoDom.Tree(this.element.children(".k-status")[0]);
        },

        _toolbar: function () {
            var options = this.options.toolbar;
            var toolbar = this.toolbar;

            if (!options) {
                return;
            }

            if ($.isArray(options)) {
                var buttons = this._buildCommands(options);
                new kendoDom.Tree(toolbar[0]).render(buttons);
            } else {
                toolbar.append(kendo.template(options)({}));
            }

            this.angular("compile", function () {
                return { elements: toolbar.get() };
            });
        },

        _render: function (options) {
            options = options || {};

            var messages = this.options.messages;
            var data = this.dataSource.rootNodes();
            var aggregates = this.dataSource.aggregates();

            this._absoluteIndex = 0;

            if (options.error) {
                // root-level error message
                this._showStatus(kendo.template(
                    "#: messages.requestFailed # " +
                    "<button class='#= buttonClass #'>#: messages.retry #</button>"
                )({
                    buttonClass: [classNames.button, classNames.retry].join(" "),
                    messages: messages
                }));
            } else if (!data.length) {
                // no rows message
                this._showStatus(kendo.htmlEncode(messages.noRows));
            } else {
                // render rows
                this._hideStatus();
                this._contentTree.render(this._trs({
                    aggregates: options.aggregates,
                    data: data,
                    visible: true,
                    level: 0
                }));
            }
        },

        _ths: function () {
            var columns = this.columns;
            var filterable = this.options.filterable;
            var ths = [];
            var column, title, children, cellClasses;

            for (var i = 0, length = columns.length; i < length; i++) {
                column = columns[i];
                children = [];
                cellClasses = [classNames.header];

                if (column.headerTemplate) {
                    title = column.headerTemplate({});
                } else {
                    title = column.title || column.field || "";
                }

                if (column.sortable) {
                    children.push(kendoDomElement("a", { href: "#", className: classNames.link }, [
                        kendoTextElement(title)
                    ]));
                } else if (column.headerTemplate) {
                    children.push(kendoHtmlElement(title));
                } else {
                    children.push(kendoTextElement(title));
                }

                ths.push(kendoDomElement("th", {
                    "data-field": column.field,
                    "data-title": column.title,
                    className: cellClasses.join(" "),
                    "role": "columnheader"
                }, children));
            }

            return ths;
        },

        _colgroup: function () {
            var columns = this.columns;
            var cols = [];
            var style, width;

            for (var i = 0, length = columns.length; i < length; i++) {
                cols.push("<col ");

                width = columns[i].width;

                if (width && parseInt(width, 10) !== 0) {
                    cols.push("style='width:");
                    cols.push(typeof width === "string" ? width : width + "px");
                    cols.push("'");
                }

                cols.push("/>");
            }

            return "<colgroup>" + cols.join("") + "</colgroup>";
        },

        _trs: function (options) {
            var model, attr, className, hasChildren, childNodes, i, length, parentId;
            var rows = [];
            var level = options.level;
            var data = options.data;
            var dataSource = this.dataSource;
            var aggregates = dataSource.aggregates() || {};

            for (i = 0, length = data.length; i < length; i++) {
                className = [];

                model = data[i];

                childNodes = model.loaded() && dataSource.childNodes(model);
                hasChildren = childNodes && childNodes.length;

                attr = {
                    "data-uid": model.uid,
                    "role": "row"
                };

                if (hasChildren) {
                    attr["aria-expanded"] = !!model.expanded;
                }

                if (this._absoluteIndex % 2 !== 0) {
                    className.push(classNames.alt);
                }

                if (options.visible) {
                    this._absoluteIndex++;
                } else {
                    attr.style = { display: "none" };
                }

                if (hasChildren) {
                    className.push(classNames.group);
                }

                if (model._edit) {
                    className.push("k-grid-edit-row");
                }

                attr.className = className.join(" ") || undefined;

                rows.push(this._tds({
                    model: model,
                    attr: attr,
                    level: level
                }, proxy(this._td, this)));

                if (hasChildren) {
                    rows = rows.concat(this._trs({
                        parentId: model.id,
                        aggregates: aggregates,
                        visible: options.visible && !!model.expanded,
                        data: childNodes,
                        level: level + 1
                    }));
                }
            }

            if (this._hasFooterTemplate()) {
                parentId = options.parentId || null;

                attr = {
                    className: classNames.footerTemplate,
                    "data-parentId": parentId
                };

                if (!options.visible) {
                    attr.style = { display: "none" };
                }

                rows.push(this._tds({
                    model: aggregates[parentId],
                    attr: attr,
                    level: level
                }, this._footerTd));
            }

            return rows;
        },

        _footerTd: function (options) {
            var content = [];
            var column = options.column;
            var template = options.column.footerTemplate || $.noop;
            var aggregates = options.model[column.field] || {};
            var attr = { "role": "gridcell" };

            if (column.expandable) {
                content = content.concat(createPlaceholders({
                    level: options.level + 1,
                    className: classNames.iconPlaceHolder
                }));
            }

            if (column.attributes) {
                extend(attr, column.attributes);
            }

            content.push(kendoTextElement(template(aggregates) || ""));

            return kendoDomElement("td", attr, content);
        },

        _hasFooterTemplate: function () {
            return !!grep(this.columns, function (c) {
                return c.footerTemplate;
            }).length;
        },

        _tds: function (options, renderer) {
            var children = [];
            var columns = this.columns;
            var column;

            for (var i = 0, l = columns.length; i < l; i++) {
                column = columns[i];

                children.push(renderer({
                    model: options.model,
                    column: column,
                    level: options.level
                }));
            }

            return kendoDomElement("tr", options.attr, children);
        },

        _td: function (options) {
            var children = [];
            var model = options.model;
            var column = options.column;
            var iconClass;
            var attr = { "role": "gridcell" };

            if (model._edit && column.field && model.editable(column.field)) {
                attr[kendo.attr("container-for")] = column.field;
            } else {
                if (column.expandable) {
                    children = createPlaceholders({ level: options.level, className: classNames.iconPlaceHolder });
                    iconClass = [classNames.icon];

                    if (model.hasChildren) {
                        iconClass.push(model.expanded ? classNames.iconCollapse : classNames.iconExpand);
                    } else {
                        iconClass.push(classNames.iconHidden);
                    }

                    if (model._error) {
                        iconClass.push(classNames.refresh);
                    } else if (!model.loaded() && model.expanded) {
                        iconClass.push(classNames.loading);
                    }

                    children.push(kendoDomElement("span", { className: iconClass.join(" ") }));
                }

                if (column.attributes) {
                    extend(attr, column.attributes);
                }

                if (column.command) {
                    if (model._edit) {
                        children = this._buildCommands(["update", "canceledit"]);
                    } else {
                        children = this._buildCommands(column.command);
                    }
                } else {
                    children.push(this._cellContent(column, model));
                }
            }

            return kendoDomElement("td", attr, children);
        },

        _cellContent: function (column, model) {
            var value;

            if (column.template) {
                value = column.template(model);
            } else if (column.field) {
                value = model.get(column.field);
                if (column.format) {
                    value = kendo.format(column.format, value);
                }
            }

            if (typeof value == "undefined") {
                value = "";
            }

            if (column.template || !column.encoded) {
                return kendoHtmlElement(value);
            } else {
                return kendoTextElement(value);
            }
        },

        _buildCommands: function (commands) {
            var i, result = [];

            for (i = 0; i < commands.length; i++) {
                result.push(this._button(commands[i]));
            }

            return result;
        },

        _button: function (command) {
            var name = (command.name || command).toLowerCase();
            var text = this.options.messages.commands[name];
            var icon = [];

            command = extend({}, defaultCommands[name], { text: text }, command);

            if (command.imageClass) {
                icon.push(kendoDomElement("span", {
                    className: ["k-icon", command.imageClass].join(" ")
                }));
            }

            return kendoDomElement(
                "button", {
                    "data-command": name,
                    className: ["k-button", "k-button-icontext", command.className].join(" ")
                }, icon.concat([kendoTextElement(command.text || command.name)])
            );
        },

        _sortable: function () {
            var columns = this.columns;
            var column;
            var sortableInstance;
            var cells = this.header.find("th");
            var cell, idx, length;
            var fieldAttr = kendo.attr("field");
            var sortable = this.options.sortable;

            if (!sortable) {
                return;
            }

            for (idx = 0, length = cells.length; idx < length; idx++) {
                column = columns[idx];

                if (column.sortable !== false && !column.command && column.field) {
                    cell = cells.eq(idx);

                    sortableInstance = cell.data("kendoColumnSorter");
                    if (sortableInstance) {
                        sortableInstance.destroy();
                    }

                    cell.attr(fieldAttr, column.field)
                        .kendoColumnSorter(
                            extend({}, sortable, column.sortable, {
                                dataSource: this.dataSource
                            })
                        );
                }
            }
        },

        _filterable: function () {
            var cells = this.header.find("th");
            var filterable = this.options.filterable;
            var idx, length, column;

            if (!filterable) {
                return;
            }

            var filterInit = proxy(function (e) {
                this.trigger(FILTERMENUINIT, { field: e.field, container: e.container });
            }, this);

            for (idx = 0, length = cells.length; idx < length; idx++) {
                column = this.columns[idx];

                if (column.command || column.filterable === false) {
                    continue;
                }

                cells.eq(idx).kendoFilterMenu(extend(true, {}, filterable, column.filterable, {
                    dataSource: this.dataSource,
                    init: filterInit
                }));
            }
        },

        _change: function () {
            this.trigger(CHANGE);
        },

        _selectable: function () {
            var selectable = this.options.selectable;
            var filter;

            if (selectable) {
                selectable = kendo.ui.Selectable.parseOptions(selectable);

                filter = ">tr:not(.k-footer-template)";

                if (selectable.cell) {
                    filter = filter + ">td";
                }

                this.selectable = new kendo.ui.Selectable(this.content, {
                    filter: filter,
                    aria: true,
                    multiple: selectable.multiple,
                    change: proxy(this._change, this)
                });
            }
        },

        select: function (value) {
            var selectable = this.selectable;

            if (typeof value !== "undefined" && !selectable.options.multiple) {
                selectable.clear();

                value = value.first();
            }

            return selectable.value(value);
        },

        clearSelection: function () {
            var selected = this.select();

            if (selected.length) {
                this.selectable.clear();

                this.trigger(CHANGE);
            }
        },

        _dataSource: function (dataSource) {
            var ds = this.dataSource;

            if (ds) {
                ds.unbind(CHANGE, this._refreshHandler);
                ds.unbind(ERROR, this._errorHandler);
                ds.unbind(PROGRESS, this._progressHandler);
            }

            this._refreshHandler = proxy(this.refresh, this);
            this._errorHandler = proxy(this._error, this);
            this._progressHandler = proxy(this._progress, this);

            ds = this.dataSource = TreeListDataSource.create(dataSource);

            ds.bind(CHANGE, this._refreshHandler);
            ds.bind(ERROR, this._errorHandler);
            ds.bind(PROGRESS, this._progressHandler);
        },

        setDataSource: function (dataSource) {
            this._dataSource(dataSource);

            if (this.options.autoBind) {
                this.dataSource.fetch();
            }
        },

        dataItem: function (element) {
            var row = $(element).closest("tr");
            var model = this.dataSource.getByUid(row.attr(kendo.attr("uid")));

            return model;
        },

        editRow: function (row) {
            var model;

            if (typeof row === STRING) {
                row = this.content.find(row);
            }

            model = this.dataItem(row);

            if (!model) {
                return;
            }

            if (this._editMode() != "popup") {
                model._edit = true;
            }

            this._cancelEditor();

            this._render();

            this._createEditor(model);

            this.trigger(EDIT, {
                container: this.editor.element,
                model: model
            });
        },

        _cancelEdit: function (e) {
            e = extend(e, {
                container: this.editor.wrapper,
                model: this.editor.model
            });

            if (this.trigger(CANCEL, e)) {
                return;
            }

            this.cancelRow();
        },

        cancelRow: function () {
            this._cancelEditor();

            this._render();
        },

        saveRow: function () {
            var editor = this.editor;
            var args;

            if (!editor) {
                return;
            }

            args = {
                model: editor.model,
                container: editor.element
            };

            if (editor.end() && !this.trigger(SAVE, args)) {
                this.dataSource.sync();
            }
        },

        addRow: function (parent) {
            var editor = this.editor;
            var index = 0;
            var model = {};

            if (editor && !editor.end()) {
                return;
            }

            if (parent) {
                if (!(parent instanceof TreeListModel)) {
                    parent = this.dataItem(parent);
                }

                model.parentId = parent.id;
                index = this.dataSource.indexOf(parent) + 1;
                parent.set("expanded", true);

                this.dataSource.load(parent).then(proxy(this._insertAt, this, model, index));

                return;
            }

            this._insertAt(model, index);
        },

        _insertAt: function (model, index) {
            model = this.dataSource.insert(index, model);

            var row = this.content.find("[" + kendo.attr("uid") + "=" + model.uid + "]");

            this.editRow(row);
        },

        removeRow: function (row) {
            var model = this.dataItem(row);
            var args = {
                model: model,
                row: row
            };

            if (model && !this.trigger(REMOVE, args)) {
                this.dataSource.remove(model);

                this.dataSource.sync();
            }
        },

        _cancelEditor: function () {
            var model;
            var editor = this.editor;

            if (editor) {
                model = editor.model;

                this._destroyEditor();

                this.dataSource.cancelChanges(model);

                model._edit = false;
            }
        },

        _destroyEditor: function () {
            if (!this.editor) {
                return;
            }

            this.editor.close();
            this.editor = null;
        },

        _createEditor: function (model) {
            var row = this.content.find("[" + kendo.attr("uid") + "=" + model.uid + "]");

            var mode = this._editMode();

            var options = {
                columns: this.columns,
                model: model,
                target: this,
                clearContainer: false,
                template: this.options.editable.template
            };

            if (mode == "inline") {
                this.editor = new Editor(row, options);
            } else {
                extend(options, {
                    window: this.options.editable.window,
                    commandRenderer: proxy(function () {
                        return this._buildCommands(["update", "canceledit"]);
                    }, this),
                    fieldRenderer: this._cellContent,
                    save: proxy(this.saveRow, this),
                    cancel: proxy(this._cancelEdit, this),
                    appendTo: this.wrapper
                });

                this.editor = new PopupEditor(row, options);
            }
        },

        _editMode: function () {
            var mode = "inline",
                editable = this.options.editable;

            if (editable !== true) {
                if (typeof editable == "string") {
                    mode = editable;
                } else {
                    mode = editable.mode || mode;
                }
            }

            return mode.toLowerCase();
        }
    });

    if (kendo.ExcelMixin) {
        kendo.ExcelMixin.extend(TreeList.prototype);
    }

    if (kendo.PDFMixin) {
        kendo.PDFMixin.extend(TreeList.prototype);
    }


    extend(true, kendo.data, {
        TreeListDataSource: TreeListDataSource,
        TreeListModel: TreeListModel
    });

    extend(true, kendo.ui, {
        TreeList: TreeList
    });

    ui.plugin(TreeList);

})(window.kendo.jQuery);





/*jshint eqnull: true*/
(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        Widget = ui.Widget,
        ns = ".kendoPivotConfigurator",
        HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns,
        SETTING_CONTAINER_TEMPLATE = kendo.template('<p class="k-reset"><span class="k-icon #=icon#"></span>${name}</p>' +
                '<div class="k-list-container k-reset"/>');

    function addKPI(data) {
        var found;
        var idx = 0;
        var length = data.length;

        for (; idx < length; idx++) {
            if (data[idx].type == 2) {
                found = true;
                break;
            }
        }

        if (found) {
            data.splice(idx + 1, 0, {
                caption: "KPIs",
                defaultHierarchy: "[KPIs]",
                name: "KPIs",
                uniqueName: "[KPIs]"
            });
        }
    }

    function kpiNode(node) {
        return {
            name: node.uniqueName,
            type: node.type
        };
    }

    function normalizeKPIs(data) {
        for (var idx = 0, length = data.length; idx < length; idx++) {
            data[idx].uniqueName = data[idx].name;
            data[idx].type = "kpi";
        }

        return data;
    }

    function settingTargetFromNode(node) {
        var target = $(node).closest(".k-pivot-setting");

        if (target.length) {
            return target.data("kendoPivotSettingTarget");
        }
        return null;
    }

    var PivotConfigurator = Widget.extend({
        init: function (element, options) {
            Widget.fn.init.call(this, element, options);

            this.element.addClass("k-widget k-fieldselector k-alt k-edit-form-container");

            this._dataSource();

            this._layout();

            this.refresh();

            kendo.notify(this);
        },

        events: [],

        options: {
            name: "PivotConfigurator",
            filterable: false,
            sortable: false,
            messages: {
                measures: "Drop Data Fields Here",
                columns: "Drop Column Fields Here",
                rows: "Drop Rows Fields Here",
                measuresLabel: "Measures",
                columnsLabel: "Columns",
                rowsLabel: "Rows",
                fieldsLabel: "Fields"
            }
        },

        _dataSource: function () {
            if (this.dataSource && this._refreshHandler) {
                this.dataSource.unbind("change", this._refreshHandler);
            } else {
                this._refreshHandler = $.proxy(this.refresh, this);
            }

            this.dataSource = kendo.data.PivotDataSource.create(this.options.dataSource);
            this.dataSource.bind("change", this._refreshHandler);
        },

        setDataSource: function (dataSource) {
            this.options.dataSource = dataSource;

            this._dataSource();

            if (this.measures) {
                this.measures.setDataSource(dataSource);
            }

            if (this.rows) {
                this.rows.setDataSource(dataSource);
            }

            if (this.columns) {
                this.columns.setDataSource(dataSource);
            }

            this.refresh();
        },

        _treeViewDataSource: function () {
            var that = this;

            return kendo.data.HierarchicalDataSource.create({
                schema: {
                    model: {
                        id: "uniqueName",
                        hasChildren: function (item) {
                            return !("hierarchyUniqueName" in item) && !("aggregator" in item);
                        }
                    }
                },
                transport: {
                    read: function (options) {
                        var promise;
                        var node;
                        var kpi;

                        if ($.isEmptyObject(options.data)) {
                            promise = that.dataSource.schemaDimensions();

                            promise.done(function (data) {
                                if (!that.dataSource.cubeBuilder) {
                                    addKPI(data);
                                }
                                options.success(data);
                            })
                                    .fail(options.error);
                        } else {
                            //Hack to get the actual node as the HierarchicalDataSource does not support passing it
                            node = that.treeView.dataSource.get(options.data.uniqueName);

                            if (node.uniqueName === "[KPIs]") {
                                kpi = true;
                                promise = that.dataSource.schemaKPIs();
                                promise.done(function (data) {
                                    options.success(normalizeKPIs(data));
                                })
                                       .fail(options.error);
                            } else if (node.type == "kpi") {
                                kpi = true;
                                options.success(buildKPImeasures(node));
                            }

                            if (!kpi) {
                                if (node.type == 2) { //measure
                                    promise = that.dataSource.schemaMeasures();
                                } else if (node.dimensionUniqueName) { // hierarchy
                                    promise = that.dataSource.schemaLevels(options.data.uniqueName);
                                } else { // dimension
                                    promise = that.dataSource.schemaHierarchies(options.data.uniqueName);
                                }

                                promise.done(options.success)
                                        .fail(options.error);
                            }
                        }
                    }
                }
            });
        },

        _layout: function () {
            this.form = $('<div class="k-columns k-state-default k-floatwrap"/>').appendTo(this.element);
            this._fields();
            this._targets();
        },

        _fields: function () {
            var container = $('<div class="k-state-default"><p class="k-reset"><span class="k-icon k-i-group"></span>' + this.options.messages.fieldsLabel + '</p></div>').appendTo(this.form);

            var that = this;
            var template = '# if (item.type == 2 || item.uniqueName == "[KPIs]") { #' +
                           '<span class="k-icon k-i-#= (item.type == 2 ? \"sum\" : \"kpi\") #"></span>' +
                           '# } else if (item.type && item.type !== "kpi") { #' +
                           '<span class="k-icon k-i-dimension"></span>' +
                           '# } #' +
                           '#: item.name #';

            this.treeView = $("<div/>").appendTo(container)
                .kendoTreeView({
                    template: template,
                    dataTextField: "name",
                    dragAndDrop: true,
                    autoBind: false,
                    dataSource: this._treeViewDataSource(),
                    dragstart: function (e) {
                        var dataItem = this.dataItem(e.sourceNode);
                        if ((!dataItem.hasChildren && !dataItem.aggregator && !dataItem.measure) || (dataItem.type == 2) || dataItem.uniqueName === "[KPIs]") {
                            e.preventDefault();
                        }
                    },
                    drag: function (e) {
                        var status = "k-denied";

                        var setting = settingTargetFromNode(e.dropTarget);
                        if (setting && setting.validate(this.dataItem(e.sourceNode))) {
                            status = "k-add";
                        }

                        e.setStatusClass(status);
                    },
                    drop: function (e) {
                        e.preventDefault();

                        var setting = settingTargetFromNode(e.dropTarget);
                        var node = this.dataItem(e.sourceNode);
                        var idx, length, measures;
                        var name;

                        if (setting && setting.validate(node)) {
                            name = node.defaultHierarchy || node.uniqueName;

                            if (node.type === "kpi") {
                                measures = buildKPImeasures(node);
                                length = measures.length;
                                name = [];

                                for (idx = 0; idx < length; idx++) {
                                    name.push(kpiNode(measures[idx]));
                                }
                            } else if (node.type) {
                                name = [kpiNode(node)];
                            }

                            setting.add(name);
                        }
                    }
                })
                .data("kendoTreeView");
        },

        _createTarget: function (element, options) {
            var template = '<li class="k-item k-header" data-' + kendo.ns + 'name="${data.name}">${data.name}';
            var sortable = options.sortable;
            var icons = "";

            if (sortable) {
                icons += '#if (data.sortIcon) {#';
                icons += '<span class="k-icon ${data.sortIcon} k-setting-sort"></span>';
                icons += '#}#';
            }

            if (options.filterable || sortable) {
                icons += '<span class="k-icon k-i-arrowhead-s k-setting-fieldmenu"></span>';
            }

            icons += '<span class="k-icon k-si-close k-setting-delete"></span>';
            template += '<span class="k-field-actions">' + icons + '</span></li>';

            return new kendo.ui.PivotSettingTarget(element, $.extend({
                dataSource: this.dataSource,
                hint: function (element) {
                    var wrapper = $('<div class="k-fieldselector"><ul class="k-list k-reset"></ul></div>');

                    wrapper.find(".k-list").append(element.clone());

                    return wrapper;
                },
                template: template,
                emptyTemplate: '<li class="k-item k-empty">${data}</li>'
            }, options));
        },

        _targets: function () {
            var container = $('<div class="k-state-default"/>').appendTo(this.form);

            var columnsContainer = $(SETTING_CONTAINER_TEMPLATE({ name: this.options.messages.columnsLabel, icon: "k-i-vbars" })).appendTo(container);
            var columns = $('<ul class="k-pivot-configurator-settings k-list k-reset" />').appendTo(columnsContainer.last());

            var rowsContainer = $(SETTING_CONTAINER_TEMPLATE({ name: this.options.messages.rowsLabel, icon: "k-i-hbars" })).appendTo(container);
            var rows = $('<ul class="k-pivot-configurator-settings k-list k-reset" />').appendTo(rowsContainer.last());

            var measuresContainer = $(SETTING_CONTAINER_TEMPLATE({ name: this.options.messages.measuresLabel, icon: "k-i-sum" })).appendTo(container);
            var measures = $('<ul class="k-pivot-configurator-settings k-list k-reset" />').appendTo(measuresContainer.last());

            var options = this.options;

            this.columns = this._createTarget(columns, {
                filterable: options.filterable,
                sortable: options.sortable,
                connectWith: rows,
                messages: {
                    empty: options.messages.columns,
                    fieldMenu: options.messages.fieldMenu
                }
            });

            this.rows = this._createTarget(rows, {
                filterable: options.filterable,
                setting: "rows",
                connectWith: columns,
                messages: {
                    empty: this.options.messages.rows,
                    fieldMenu: this.options.messages.fieldMenu
                }
            });

            this.measures = this._createTarget(measures, {
                setting: "measures",
                messages: {
                    empty: options.messages.measures
                }
            });

            columns
                .add(rows)
                .add(measures)
                .on(HOVEREVENTS, ".k-item:not(.k-empty)", this._toggleHover);
        },

        _toggleHover: function (e) {
            $(e.currentTarget).toggleClass("k-state-hover", e.type === "mouseenter");
        },

        _resize: function () {
            var element = this.element;
            var height = this.options.height;
            var border, fields;

            if (!height) {
                return;
            }

            if (element.is(":visible")) {
                element.height(height);

                fields = element.children(".k-columns")
                                .children("div.k-state-default");

                border = (element.outerHeight() - element.innerHeight()) / 2;
                height = height - (fields.outerHeight(true) - fields.height()) - border;

                fields.height(height);
            }
        },

        refresh: function () {
            var dataSource = this.dataSource;

            if (dataSource.cubeBuilder || this._cube !== dataSource.cube() || this._catalog !== dataSource.catalog()) {
                this.treeView.dataSource.fetch();
            }

            this._catalog = this.dataSource.catalog();
            this._cube = this.dataSource.cube();

            this._resize();
        },

        destroy: function () {
            Widget.fn.destroy.call(this);

            this.dataSource.unbind("change", this._refreshHandler);

            this.form.find(".k-list").off(ns);

            this.rows.destroy();
            this.columns.destroy();
            this.measures.destroy();
            this.treeView.destroy();

            this.element = null;
            this._refreshHandler = null;
        }
    });

    function kpiMeasure(name, measure, type) {
        return {
            hierarchyUniqueName: name,
            uniqueName: measure,
            caption: measure,
            measure: measure,
            name: measure,
            type: type
        };
    }

    function buildKPImeasures(node) {
        var name = node.name;
        return [
            kpiMeasure(name, node.value, "value"),
            kpiMeasure(name, node.goal, "goal"),
            kpiMeasure(name, node.status, "status"),
            kpiMeasure(name, node.trend, "trend")
        ];
    }

    ui.plugin(PivotConfigurator);

})(window.kendo.jQuery);





(function ($, angular, undefined) {
    "use strict";

    if (!angular) {
        return;
    }

    /*jshint eqnull:true,loopfunc:true,-W052,-W028  */

    var module = angular.module('kendo.directives', []),
        $injector = angular.injector(['ng']),
        $parse = $injector.get('$parse'),
        $timeout = $injector.get('$timeout'),
        $defaultCompile,
        $log = $injector.get('$log');

    function withoutTimeout(f) {
        var save = $timeout;
        try {
            $timeout = function (f) { return f(); };
            return f();
        } finally {
            $timeout = save;
        }
    }

    var OPTIONS_NOW;

    var createDataSource = (function () {
        var types = {
            TreeList: 'TreeListDataSource',
            TreeView: 'HierarchicalDataSource',
            Scheduler: 'SchedulerDataSource',
            PanelBar: '$PLAIN',
            Menu: "$PLAIN",
            ContextMenu: "$PLAIN"
        };
        var toDataSource = function (dataSource, type) {
            if (type == '$PLAIN') {
                return dataSource;
            }
            return kendo.data[type].create(dataSource);
        };
        return function (scope, element, role, source) {
            var type = types[role] || 'DataSource';
            var ds = toDataSource(scope.$eval(source), type);

            // not recursive -- this triggers when the whole data source changed
            scope.$watch(source, function (mew, old) {
                if (mew !== old) {
                    var ds = toDataSource(mew, type);
                    var widget = kendoWidgetInstance(element);
                    if (widget && typeof widget.setDataSource == "function") {
                        widget.setDataSource(ds);
                    }
                }
            });
            return ds;
        };
    } ());

    var ignoredAttributes = {
        kDataSource: true,
        kOptions: true,
        kRebind: true,
        kNgModel: true,
        kNgDelay: true
    };

    var ignoredOwnProperties = {
        // XXX: other names to ignore here?
        name: true,
        title: true,
        style: true
    };

    function addOption(scope, options, name, value) {
        options[name] = angular.copy(scope.$eval(value));
        if (options[name] === undefined && value.match(/^\w*$/)) {
            $log.warn(name + ' attribute resolved to undefined. Maybe you meant to use a string literal like: \'' + value + '\'?');
        }
    }

    function createWidget(scope, element, attrs, widget, origAttr) {
        var kNgDelay = attrs.kNgDelay,
            delayValue = scope.$eval(kNgDelay);

        if (kNgDelay && !delayValue) {
            var unregister = scope.$watch(kNgDelay, function (newValue, oldValue) {
                if (newValue !== oldValue) {
                    unregister();
                    // remove subsequent delays, to make ng-rebind work
                    element.removeAttr(attrs.$attr.kNgDelay);
                    kNgDelay = null;
                    $timeout(createIt); // XXX: won't work without `timeout` ;-\
                }
            });

            return;
        } else {
            return createIt();
        }

        function createIt() {
            var originalElement;

            if (attrs.kRebind) {
                originalElement = $($(element)[0].cloneNode(true));
            }


            var role = widget.replace(/^kendo/, '');
            var options = angular.extend({}, attrs.defaultOptions, scope.$eval(attrs.kOptions || attrs.options));
            var ctor = $(element)[widget];

            if (!ctor) {
                window.console.error("Could not find: " + widget);
                return null;
            }

            var widgetOptions = ctor.widget.prototype.options;
            var widgetEvents = ctor.widget.prototype.events;

            $.each(attrs, function (name, value) {
                if (name === "source" || name === "kDataSource") {
                    return;
                }

                var dataName = "data" + name.charAt(0).toUpperCase() + name.slice(1);

                if (name.indexOf("on") === 0) { // let's search for such event.
                    var eventKey = name.replace(/^on./, function (prefix) {
                        return prefix.charAt(2).toLowerCase();
                    });

                    if (widgetEvents.indexOf(eventKey) > -1) {
                        options[eventKey] = value;
                    }
                } // don't elsif here - there are on* options

                if (widgetOptions.hasOwnProperty(dataName)) {
                    addOption(scope, options, dataName, value);
                } else if (widgetOptions.hasOwnProperty(name) && !ignoredOwnProperties[name]) {
                    addOption(scope, options, name, value);
                } else if (!ignoredAttributes[name]) {
                    var match = name.match(/^k(On)?([A-Z].*)/);
                    if (match) {
                        var optionName = match[2].charAt(0).toLowerCase() + match[2].slice(1);
                        if (match[1] && name != "kOnLabel" // XXX: k-on-label can be used on MobileSwitch :-\
                        ) {
                            options[optionName] = value;
                        } else {
                            if (name == "kOnLabel") {
                                optionName = "onLabel"; // XXX: that's awful.
                            }
                            addOption(scope, options, optionName, value);
                        }
                    }
                }
            });

            // parse the datasource attribute
            var dataSource = attrs.kDataSource || attrs.source;

            if (dataSource) {
                options.dataSource = createDataSource(scope, element, role, dataSource);
            }

            // deepExtend in kendo.core (used in Editor) will fail with stack
            // overflow if we don't put it in an array :-\
            options.$angular = [scope];

            if (element.is("select")) {
                (function (options) {
                    if (options.length > 0) {
                        var first = $(options[0]);
                        if (!/\S/.test(first.text()) && /^\?/.test(first.val())) {
                            first.remove();
                        }
                    }
                } (element[0].options));
            }

            var object = ctor.call(element, OPTIONS_NOW = options).data(widget);

            exposeWidget(object, scope, attrs, widget, origAttr);

            scope.$emit("kendoWidgetCreated", object);

            var destroyRegister = destroyWidgetOnScopeDestroy(scope, object);

            if (attrs.kRebind) {
                setupRebind(object, scope, element, originalElement, attrs.kRebind, destroyRegister);
            }

            return object;
        }
    }

    function exposeWidget(widget, scope, attrs, kendoWidget, origAttr) {
        if (attrs[origAttr]) {
            var set = $parse(attrs[origAttr]).assign;
            if (set) {
                // set the value of the expression to the kendo widget object to expose its api
                set(scope, widget);
            } else {
                throw new Error(origAttr + ' attribute used but expression in it is not assignable: ' + attrs[kendoWidget]);
            }
        }
    }

    function formValue(element) {
        if (/checkbox|radio/i.test(element.attr("type"))) {
            return element.prop("checked");
        }
        return element.val();
    }

    var formRegExp = /^(input|select|textarea)$/i;

    function isForm(element) {
        return formRegExp.test(element[0].tagName);
    }

    function bindToNgModel(widget, scope, element, ngModel, ngForm) {
        if (!widget.value) {
            return;
        }

        var value;

        if (isForm(element)) {
            value = function () {
                return formValue(element);
            };
        } else {
            value = function () {
                return widget.value();
            };
        }

        // Angular will invoke $render when the view needs to be updated with the view value.
        ngModel.$render = function () {
            // Update the widget with the view value.

            // delaying with setTimout for cases where the datasource is set thereafter.
            // https://github.com/kendo-labs/angular-kendo/issues/304
            var val = ngModel.$viewValue;
            if (val === undefined) {
                val = ngModel.$modelValue;
            }
            setTimeout(function () {
                if (widget) { // might have been destroyed in between. :-(
                    widget.value(val);
                }
            }, 0);
        };

        // Some widgets trigger "change" on the input field
        // and this would result in two events sent (#135)
        var haveChangeOnElement = false;

        if (isForm(element)) {
            element.on("change", function () {
                haveChangeOnElement = true;
            });
        }

        var onChange = function (pristine) {
            return function () {
                var formPristine;
                if (haveChangeOnElement) {
                    return;
                }
                haveChangeOnElement = false;
                if (pristine && ngForm) {
                    formPristine = ngForm.$pristine;
                }
                ngModel.$setViewValue(value());
                if (pristine) {
                    ngModel.$setPristine();
                    if (formPristine) {
                        ngForm.$setPristine();
                    }
                }
                digest(scope);
            };
        };

        widget.first("change", onChange(false));
        widget.first("dataBound", onChange(true));

        var currentVal = value();

        // if the model value is undefined, then we set the widget value to match ( == null/undefined )
        if (currentVal != ngModel.$viewValue) {
            if (!ngModel.$isEmpty(ngModel.$viewValue)) {
                widget.value(ngModel.$viewValue);
            } else if (currentVal != null && currentVal !== "" && currentVal != ngModel.$viewValue) {
                ngModel.$setViewValue(currentVal);
            }
        }

        ngModel.$setPristine();
    }

    function bindToKNgModel(widget, scope, kNgModel) {
        if (typeof widget.value != "function") {
            $log.warn("k-ng-model specified on a widget that does not have the value() method: " + (widget.options.name));
            return;
        }

        var getter = $parse(kNgModel);
        var setter = getter.assign;
        var updating = false;

        widget.$angular_setLogicValue(getter(scope));

        // keep in sync
        scope.$watch(kNgModel, function (newValue, oldValue) {
            if (newValue === undefined) {
                // because widget's value() method usually checks if the new value is undefined,
                // in which case it returns the current value rather than clearing the field.
                // https://github.com/telerik/kendo-ui-core/issues/299
                newValue = null;
            }
            if (updating) {
                return;
            }
            if (newValue === oldValue) {
                return;
            }
            widget.$angular_setLogicValue(newValue);
        });

        widget.first("change", function () {
            updating = true;
            scope.$apply(function () {
                setter(scope, widget.$angular_getLogicValue());
            });
            updating = false;
        });
    }

    function destroyWidgetOnScopeDestroy(scope, widget) {
        var deregister = scope.$on("$destroy", function () {
            deregister();
            if (widget) {
                if (widget.element) {
                    widget = kendoWidgetInstance(widget.element);
                    if (widget) {
                        widget.destroy();
                    }
                }
                widget = null;
            }
        });

        return deregister;
    }

    // mutation observers — propagate the original
    // element's class to the widget wrapper.
    function propagateClassToWidgetWrapper(widget, element) {
        if (!(window.MutationObserver && widget.wrapper)) {
            return;
        }

        var prevClassList = [].slice.call($(element)[0].classList);

        var mo = new MutationObserver(function (changes) {
            suspend();    // make sure we don't trigger a loop
            if (!widget) {
                return;
            }

            changes.forEach(function (chg) {
                var w = $(widget.wrapper)[0];
                switch (chg.attributeName) {

                    case "class":
                        // sync classes to the wrapper element
                        var currClassList = [].slice.call(chg.target.classList);
                        currClassList.forEach(function (cls) {
                            if (prevClassList.indexOf(cls) < 0) {
                                w.classList.add(cls);
                                if (kendo.ui.ComboBox && widget instanceof kendo.ui.ComboBox) { // https://github.com/kendo-labs/angular-kendo/issues/356
                                    widget.input[0].classList.add(cls);
                                }
                            }
                        });
                        prevClassList.forEach(function (cls) {
                            if (currClassList.indexOf(cls) < 0) {
                                w.classList.remove(cls);
                                if (kendo.ui.ComboBox && widget instanceof kendo.ui.ComboBox) { // https://github.com/kendo-labs/angular-kendo/issues/356
                                    widget.input[0].classList.remove(cls);
                                }
                            }
                        });
                        prevClassList = currClassList;
                        break;

                    case "disabled":
                        if (typeof widget.enable == "function") {
                            widget.enable(!$(chg.target).attr("disabled"));
                        }
                        break;

                    case "readonly":
                        if (typeof widget.readonly == "function") {
                            widget.readonly(!!$(chg.target).attr("readonly"));
                        }
                        break;
                }
            });

            resume();
        });

        function suspend() {
            mo.disconnect();
        }

        function resume() {
            mo.observe($(element)[0], { attributes: true });
        }

        resume();
        widget.first("destroy", suspend);
    }

    function setupRebind(widget, scope, element, originalElement, rebindAttr, destroyRegister) {
        // watch for changes on the expression passed in the k-rebind attribute
        var unregister = scope.$watch(rebindAttr, function (newValue, oldValue) {
            if (newValue !== oldValue) {
                unregister(); // this watcher will be re-added if we compile again!

                /****************************************************************
                // XXX: this is a gross hack that might not even work with all
                // widgets.  we need to destroy the current widget and get its
                // wrapper element out of the DOM, then make the original element
                // visible so we can initialize a new widget on it.
                //
                // kRebind is probably impossible to get right at the moment.
                ****************************************************************/

                var _wrapper = $(widget.wrapper)[0];
                var _element = $(widget.element)[0];
                var compile = element.injector().get("$compile");
                widget.destroy();

                if (destroyRegister) {
                    destroyRegister();
                }

                widget = null;

                if (_wrapper && _element) {
                    _wrapper.parentNode.replaceChild(_element, _wrapper);
                    $(element).replaceWith(originalElement);
                }

                compile(originalElement)(scope);
            }
        }, true); // watch for object equality. Use native or simple values.
        digest(scope);
    }

    module.factory('directiveFactory', ['$compile', function (compile) {
        var KENDO_COUNT = 0;
        var RENDERED = false;

        // caching $compile for the dirty hack upstairs. This is awful, but we happen to have elements outside of the bootstrapped root :(.
        $defaultCompile = compile;

        var create = function (role, origAttr) {

            return {
                // Parse the directive for attributes and classes
                restrict: "AC",
                require: ["?ngModel", "^?form"],
                scope: false,

                controller: ['$scope', '$attrs', '$element', function ($scope, $attrs, $element) {
                    this.template = function (key, value) {
                        $attrs[key] = kendo.stringify(value);
                    };
                } ],

                link: function (scope, element, attrs, controllers) {
                    var ngModel = controllers[0];
                    var ngForm = controllers[1];
                    var $element = $(element);

                    // we must remove data-kendo-widget-name attribute because
                    // it breaks kendo.widgetInstance; can generate all kinds
                    // of funny issues like
                    //
                    //   https://github.com/kendo-labs/angular-kendo/issues/167
                    //
                    // but we still keep the attribute without the
                    // `data-` prefix, so k-rebind would work.
                    var roleattr = role.replace(/([A-Z])/g, "-$1");
                    var isVisible = $element.css("visibility") !== "hidden";

                    $element.attr(roleattr, $element.attr("data-" + roleattr));
                    $element[0].removeAttribute("data-" + roleattr);

                    if (isVisible) {
                        $element.css("visibility", "hidden");
                    }

                    ++KENDO_COUNT;

                    $timeout(function () {
                        if (isVisible) {
                            $element.css("visibility", "");
                        }
                        var widget = createWidget(scope, element, attrs, role, origAttr);

                        if (!widget) {
                            return;
                        }

                        // 2 way binding: ngModel <-> widget.value()
                        if (ngModel) {
                            bindToNgModel(widget, scope, element, ngModel, ngForm);
                        }

                        // kNgModel is used for the "logical" value
                        if (attrs.kNgModel) {
                            bindToKNgModel(widget, scope, attrs.kNgModel);
                        }

                        propagateClassToWidgetWrapper(widget, element);

                        --KENDO_COUNT;
                        if (KENDO_COUNT === 0) {
                            if (!RENDERED) {
                                RENDERED = true;
                                scope.$emit("kendoRendered");
                                $("form").each(function () {
                                    var form = $(this).controller("form");
                                    if (form) {
                                        form.$setPristine();
                                    }
                                });
                            }
                        }
                    });
                }
            };
        };

        return {
            create: create
        };
    } ]);

    var TAGNAMES = {
        Editor: "textarea",
        NumericTextBox: "input",
        DatePicker: "input",
        DateTimePicker: "input",
        TimePicker: "input",
        AutoComplete: "input",
        ColorPicker: "input",
        MaskedTextBox: "input",
        MultiSelect: "input",
        Upload: "input",
        Validator: "form",
        Button: "button",
        MobileButton: "a",
        MobileBackButton: "a",
        MobileDetailButton: "a",
        ListView: "ul",
        MobileListView: "ul",
        TreeView: "ul",
        Menu: "ul",
        ContextMenu: "ul",
        ActionSheet: "ul"
    };

    var SKIP_SHORTCUTS = [
        'MobileView',
        'MobileLayout',
        'MobileSplitView',
        'MobilePane',
        'MobileModalView'
    ];

    var MANUAL_DIRECTIVES = [
        'MobileApplication',
        'MobileView',
        'MobileModalView',
        'MobileLayout',
        'MobileActionSheet',
        'MobileDrawer',
        'MobileSplitView',
        'MobilePane',
        'MobileScrollView',
        'MobilePopOver'
    ];

    angular.forEach(['MobileNavBar', 'MobileButton', 'MobileBackButton', 'MobileDetailButton', 'MobileTabStrip', 'MobileScrollView'], function (widget) {
        MANUAL_DIRECTIVES.push(widget);
        widget = "kendo" + widget;
        module.directive(widget, function () {
            return {
                restrict: "A",
                link: function (scope, element, attrs, controllers) {
                    createWidget(scope, element, attrs, widget, widget);
                }
            };
        });
    });

    function createDirectives(klass, isMobile) {
        function make(directiveName, widgetName) {
            module.directive(directiveName, [
                "directiveFactory",
                function (directiveFactory) {
                    return directiveFactory.create(widgetName, directiveName);
                }
            ]);
        }

        var name = isMobile ? "Mobile" : "";
        name += klass.fn.options.name;

        var className = name;
        var shortcut = "kendo" + name.charAt(0) + name.substr(1).toLowerCase();
        name = "kendo" + name;

        // <kendo-numerictextbox>-type directives
        var dashed = name.replace(/([A-Z])/g, "-$1");

        if (SKIP_SHORTCUTS.indexOf(name.replace("kendo", "")) == -1) {
            var names = name === shortcut ? [name] : [name, shortcut];
            angular.forEach(names, function (directiveName) {
                module.directive(directiveName, function () {
                    return {
                        restrict: "E",
                        replace: true,
                        template: function (element, attributes) {
                            var tag = TAGNAMES[className] || "div";
                            return "<" + tag + " " + dashed + ">" + element.html() + "</" + tag + ">";
                        }
                    };
                });
            });
        }

        if (MANUAL_DIRECTIVES.indexOf(name.replace("kendo", "")) > -1) {
            return;
        }

        // here name should be like kendoMobileListView so kendo-mobile-list-view works,
        // and shortcut like kendoMobilelistview, for kendo-mobilelistview

        make(name, name);
        if (shortcut != name) {
            make(shortcut, name);
        }

    }

    (function () {
        function doAll(isMobile) {
            return function (namespace) {
                angular.forEach(namespace, function (value) {
                    if (value.fn && value.fn.options && value.fn.options.name && (/^[A-Z]/).test(value.fn.options.name)) {
                        createDirectives(value, isMobile);
                    }
                });
            };
        }
        angular.forEach([kendo.ui, kendo.dataviz && kendo.dataviz.ui], doAll(false));
        angular.forEach([kendo.mobile && kendo.mobile.ui], doAll(true));
    })();

    /* -----[ utils ]----- */

    function kendoWidgetInstance(el) {
        el = $(el);
        return kendo.widgetInstance(el, kendo.ui) ||
            kendo.widgetInstance(el, kendo.mobile.ui) ||
            kendo.widgetInstance(el, kendo.dataviz.ui);
    }

    function digest(scope, func) {
        var root = scope.$root || scope;
        var isDigesting = /^\$(digest|apply)$/.test(root.$$phase);
        if (func) {
            if (isDigesting) {
                func();
            } else {
                root.$apply(func);
            }
        } else if (!isDigesting) {
            root.$digest();
        }
    }

    function destroyScope(scope, el) {
        scope.$destroy();
        if (el) {
            // prevent leaks. https://github.com/kendo-labs/angular-kendo/issues/237
            $(el)
                .removeData("$scope")
                .removeData("$isolateScope")
                .removeData("$isolateScopeNoTemplate")
                .removeClass("ng-scope");
        }
    }

    var pendingPatches = [];

    // defadvice will patch a class' method with another function.  That
    // function will be called in a context containing `next` (to call
    // the next method) and `object` (a reference to the original
    // object).
    function defadvice(klass, methodName, func) {
        if ($.isArray(klass)) {
            return angular.forEach(klass, function (klass) {
                defadvice(klass, methodName, func);
            });
        }
        if (typeof klass == "string") {
            var a = klass.split(".");
            var x = kendo;
            while (x && a.length > 0) {
                x = x[a.shift()];
            }
            if (!x) {
                pendingPatches.push([klass, methodName, func]);
                return false;
            }
            klass = x.prototype;
        }
        var origMethod = klass[methodName];
        klass[methodName] = function () {
            var self = this, args = arguments;
            return func.apply({
                self: self,
                next: function () {
                    return origMethod.apply(self, arguments.length > 0 ? arguments : args);
                }
            }, args);
        };
        return true;
    }

    defadvice(kendo.ui, "plugin", function (klass, register, prefix) {
        this.next();
        pendingPatches = $.grep(pendingPatches, function (args) {
            return !defadvice.apply(null, args);
        });
        createDirectives(klass, prefix == "Mobile");
    });

    /* -----[ Customize widgets for Angular ]----- */

    defadvice(["ui.Widget", "mobile.ui.Widget"], "angular", function (cmd, arg) {
        var self = this.self;
        if (cmd == "init") {
            // `arg` here should be the widget options.
            // the Chart doesn't send the options to Widget::init in constructor
            // hence the OPTIONS_NOW hack (initialized in createWidget).
            if (!arg && OPTIONS_NOW) {
                arg = OPTIONS_NOW;
            }
            OPTIONS_NOW = null;
            if (arg && arg.$angular) {
                self.$angular_scope = arg.$angular[0];
                self.$angular_init(self.element, arg);
            }
            return;
        }

        var scope = self.$angular_scope; //  || angular.element(self.element).scope();

        if (scope) {
            withoutTimeout(function () {
                var x = arg(), elements = x.elements, data = x.data;
                if (elements.length > 0) {
                    switch (cmd) {

                        case "cleanup":
                            angular.forEach(elements, function (el) {
                                var itemScope = angular.element(el).scope();
                                if (itemScope && itemScope !== scope && itemScope.$$kendoScope) {
                                    destroyScope(itemScope, el);
                                }
                            });
                            break;

                        case "compile":
                            var injector = self.element.injector();
                            // gross gross gross hack :(. Works for popups that may be out of the ng-app directive.
                            // they don't have injectors. Same thing happens in our tests, too.
                            var compile = injector ? injector.get("$compile") : $defaultCompile;

                            angular.forEach(elements, function (el, i) {
                                var itemScope;
                                if (x.scopeFrom) {
                                    itemScope = angular.element(x.scopeFrom).scope();
                                } else {
                                    var vars = data && data[i];
                                    if (vars !== undefined) {
                                        itemScope = $.extend(scope.$new(), vars);
                                        itemScope.$$kendoScope = true;
                                    }
                                }

                                compile(el)(itemScope || scope);
                            });
                            digest(scope);
                            break;
                    }
                }
            });
        }
    });

    defadvice("ui.Widget", "$angular_getLogicValue", function () {
        return this.self.value();
    });

    defadvice("ui.Widget", "$angular_setLogicValue", function (val) {
        this.self.value(val);
    });

    defadvice("ui.Select", "$angular_getLogicValue", function () {
        var item = this.self.dataItem();
        if (item) {
            if (this.self.options.valuePrimitive) {
                return item[this.self.options.dataValueField];
            } else {
                return item.toJSON();
            }
        } else {
            return null;
        }
    });

    defadvice("ui.Select", "$angular_setLogicValue", function (val) {
        var self = this.self;
        var options = self.options;
        var valueField = options.dataValueField;

        if (valueField && !options.valuePrimitive) {
            val = val != null ? val[options.dataValueField || options.dataTextField] : null;
        }

        self.value(val);
    });

    defadvice("ui.MultiSelect", "$angular_getLogicValue", function () {
        var value = this.self.dataItems();
        var valueField = this.self.options.dataValueField;

        if (valueField && this.self.options.valuePrimitive) {
            value = $.map(value, function (item) {
                return item[valueField];
            });
        }

        return value;
    });

    defadvice("ui.MultiSelect", "$angular_setLogicValue", function (val) {
        if (val == null) {
            val = [];
        }
        var self = this.self,
            valueField = self.options.dataValueField;

        if (valueField && !self.options.valuePrimitive) {
            val = $.map(val, function (item) {
                return item[valueField];
            });
        }

        self.value(val);
    });

    defadvice("ui.AutoComplete", "$angular_getLogicValue", function () {
        var options = this.self.options;

        var values = this.self.value().split(options.separator);
        var valuePrimitive = options.valuePrimitive;
        var data = this.self.dataSource.data();
        var dataItems = [];
        for (var idx = 0, length = data.length; idx < length; idx++) {
            var item = data[idx];
            var dataValue = options.dataTextField ? item[options.dataTextField] : item;
            for (var j = 0; j < values.length; j++) {
                if (dataValue === values[j]) {
                    if (valuePrimitive) {
                        dataItems.push(dataValue);
                    } else {
                        dataItems.push(item.toJSON());
                    }

                    break;
                }
            }
        }

        return dataItems;
    });

    defadvice("ui.AutoComplete", "$angular_setLogicValue", function (value) {
        if (value == null) {
            value = [];
        }

        var self = this.self,
            dataTextField = self.options.dataTextField;

        if (dataTextField && !self.options.valuePrimitive) {
            value = $.map(value, function (item) {
                return item[dataTextField];
            });
        }

        self.value(value);
    });

    // All event handlers that are strings are compiled the Angular way.
    defadvice("ui.Widget", "$angular_init", function (element, options) {
        var self = this.self;
        if (options && !$.isArray(options)) {
            var scope = self.$angular_scope;
            for (var i = self.events.length; --i >= 0; ) {
                var event = self.events[i];
                var handler = options[event];
                if (handler && typeof handler == "string") {
                    options[event] = self.$angular_makeEventHandler(event, scope, handler);
                }
            }
        }
    });

    // most handers will only contain a kendoEvent in the scope.
    defadvice("ui.Widget", "$angular_makeEventHandler", function (event, scope, handler) {
        handler = $parse(handler);
        return function (e) {
            digest(scope, function () {
                handler(scope, { kendoEvent: e });
            });
        };
    });

    // for the Grid and ListView we add `data` and `selected` too.
    defadvice(["ui.Grid", "ui.ListView", "ui.TreeView"], "$angular_makeEventHandler", function (event, scope, handler) {
        if (event != "change") {
            return this.next();
        }
        handler = $parse(handler);
        return function (ev) {
            var widget = ev.sender;
            var options = widget.options;
            var cell, multiple, locals = { kendoEvent: ev }, elems, items, columns, colIdx;

            if (angular.isString(options.selectable)) {
                cell = options.selectable.indexOf('cell') !== -1;
                multiple = options.selectable.indexOf('multiple') !== -1;
            }

            elems = locals.selected = this.select();
            items = locals.data = [];
            columns = locals.columns = [];
            for (var i = 0; i < elems.length; i++) {
                var item = cell ? elems[i].parentNode : elems[i];
                var dataItem = widget.dataItem(item);
                if (cell) {
                    if (angular.element.inArray(dataItem, items) < 0) {
                        items.push(dataItem);
                    }
                    colIdx = angular.element(elems[i]).index();
                    if (angular.element.inArray(colIdx, columns) < 0) {
                        columns.push(colIdx);
                    }
                } else {
                    items.push(dataItem);
                }
            }

            if (!multiple) {
                locals.dataItem = locals.data = items[0];
                locals.selected = elems[0];
            }

            digest(scope, function () {
                handler(scope, locals);
            });
        };
    });

    // If no `template` is supplied for Grid columns, provide an Angular
    // template.  The reason is that in this way AngularJS will take
    // care to update the view as the data in scope changes.
    defadvice("ui.Grid", "$angular_init", function (element, options) {
        this.next();
        if (options.columns) {
            var settings = $.extend({}, kendo.Template, options.templateSettings);
            angular.forEach(options.columns, function (col) {
                if (col.field && !col.template && !col.format && !col.values && (col.encoded === undefined || col.encoded)) {
                    col.template = "<span ng-bind='" +
                        kendo.expr(col.field, "dataItem") + "'>#: " +
                        kendo.expr(col.field, settings.paramName) + "#</span>";
                }
            });
        }
    });

    {
        // mobile/ButtonGroup does not have a "value" method, but looks
        // like it would be useful.  We provide it here.

        defadvice("mobile.ui.ButtonGroup", "value", function (mew) {
            var self = this.self;
            if (mew != null) {
                self.select(self.element.children("li.km-button").eq(mew));
                self.trigger("change");
                self.trigger("select", { index: self.selectedIndex });
            }
            return self.selectedIndex;
        });

        defadvice("mobile.ui.ButtonGroup", "_select", function () {
            this.next();
            this.self.trigger("change");
        });
    }

    // mobile directives
    module
    .directive('kendoMobileApplication', function () {
        return {
            terminal: true,
            link: function (scope, element, attrs, controllers) {
                createWidget(scope, element, attrs, 'kendoMobileApplication', 'kendoMobileApplication');
            }
        };
    }).directive('kendoMobileView', function () {
        return {
            scope: true,
            link: {
                pre: function (scope, element, attrs, controllers) {
                    attrs.defaultOptions = scope.viewOptions;
                    attrs._instance = createWidget(scope, element, attrs, 'kendoMobileView', 'kendoMobileView');
                },

                post: function (scope, element, attrs) {
                    attrs._instance._layout();
                    attrs._instance._scroller();
                }
            }
        };
    }).directive('kendoMobileDrawer', function () {
        return {
            scope: true,
            link: {
                pre: function (scope, element, attrs, controllers) {
                    attrs.defaultOptions = scope.viewOptions;
                    attrs._instance = createWidget(scope, element, attrs, 'kendoMobileDrawer', 'kendoMobileDrawer');
                },

                post: function (scope, element, attrs) {
                    attrs._instance._layout();
                    attrs._instance._scroller();
                }
            }
        };
    }).directive('kendoMobileModalView', function () {
        return {
            scope: true,
            link: {
                pre: function (scope, element, attrs, controllers) {
                    attrs.defaultOptions = scope.viewOptions;
                    attrs._instance = createWidget(scope, element, attrs, 'kendoMobileModalView', 'kendoMobileModalView');
                },

                post: function (scope, element, attrs) {
                    attrs._instance._layout();
                    attrs._instance._scroller();
                }
            }
        };
    }).directive('kendoMobileSplitView', function () {
        return {
            terminal: true,
            link: {
                pre: function (scope, element, attrs, controllers) {
                    attrs.defaultOptions = scope.viewOptions;
                    attrs._instance = createWidget(scope, element, attrs, 'kendoMobileSplitView', 'kendoMobileSplitView');
                },

                post: function (scope, element, attrs) {
                    attrs._instance._layout();
                }
            }
        };
    }).directive('kendoMobilePane', function () {
        return {
            terminal: true,
            link: {
                pre: function (scope, element, attrs, controllers) {
                    attrs.defaultOptions = scope.viewOptions;
                    createWidget(scope, element, attrs, 'kendoMobilePane', 'kendoMobilePane');
                }
            }
        };
    }).directive('kendoMobileLayout', function () {
        return {
            link: {
                pre: function (scope, element, attrs, controllers) {
                    createWidget(scope, element, attrs, 'kendoMobileLayout', 'kendoMobileLayout');
                }
            }
        };
    }).directive('kendoMobileActionSheet', function () {
        return {
            restrict: "A",
            link: function (scope, element, attrs, controllers) {
                element.find("a[k-action]").each(function () {
                    $(this).attr("data-" + kendo.ns + "action", $(this).attr("k-action"));
                });

                createWidget(scope, element, attrs, 'kendoMobileActionSheet', 'kendoMobileActionSheet');
            }
        };
    }).directive('kendoMobilePopOver', function () {
        return {
            terminal: true,
            link: {
                pre: function (scope, element, attrs, controllers) {
                    attrs.defaultOptions = scope.viewOptions;
                    createWidget(scope, element, attrs, 'kendoMobilePopOver', 'kendoMobilePopOver');
                }
            }
        };
    }).directive('kendoViewTitle', function () {
        return {
            restrict: "E",
            replace: true,
            template: function (element, attributes) {
                return "<span data-" + kendo.ns + "role='view-title'>" + element.html() + "</span>";
            }
        };
    }).directive('kendoMobileHeader', function () {
        return {
            restrict: "E",
            link: function (scope, element, attrs, controllers) {
                element.addClass("km-header").attr("data-role", "header");
            }
        };
    }).directive('kendoMobileFooter', function () {
        return {
            restrict: 'E',
            link: function (scope, element, attrs, controllers) {
                element.addClass("km-footer").attr("data-role", "footer");
            }
        };
    }).directive('kendoMobileScrollViewPage', function () {
        return {
            restrict: "E",
            replace: true,
            template: function (element, attributes) {
                return "<div data-" + kendo.ns + "role='page'>" + element.html() + "</div>";
            }
        };
    });

    angular.forEach(['align', 'icon', 'rel', 'transition', 'actionsheetContext'], function (attr) {
        var kAttr = "k" + attr.slice(0, 1).toUpperCase() + attr.slice(1);

        module.directive(kAttr, function () {
            return {
                restrict: 'A',
                priority: 2,
                link: function (scope, element, attrs) {
                    element.attr(kendo.attr(kendo.toHyphens(attr)), scope.$eval(attrs[kAttr]));
                }
            };
        });
    });

    var WIDGET_TEMPLATE_OPTIONS = {
        "TreeMap": ["Template"],
        "MobileListView": ["HeaderTemplate", "Template"],
        "MobileScrollView": ["EmptyTemplate", "Template"],
        "Grid": ["AltRowTemplate", "DetailTemplate", "RowTemplate"],
        "ListView": ["EditTemplate", "Template", "AltTemplate"],
        "Pager": ["SelectTemplate", "LinkTemplate"],
        "PivotGrid": ["ColumnHeaderTemplate", "DataCellTemplate", "RowHeaderTemplate"],
        "Scheduler": ["AllDayEventTemplate", "DateHeaderTemplate", "EventTemplate", "MajorTimeHeaderTemplate", "MinorTimeHeaderTemplate"],
        "TreeView": ["Template"],
        "Validator": ["ErrorTemplate"]
    };

    (function () {
        var templateDirectives = {};
        angular.forEach(WIDGET_TEMPLATE_OPTIONS, function (templates, widget) {
            angular.forEach(templates, function (template) {
                if (!templateDirectives[template]) {
                    templateDirectives[template] = [];
                }
                templateDirectives[template].push("?^^kendo" + widget);
            });
        });

        angular.forEach(templateDirectives, function (parents, directive) {
            var templateName = "k" + directive;
            var attrName = kendo.toHyphens(templateName);

            module.directive(templateName, function () {
                return {
                    restrict: "A",
                    require: parents,
                    terminal: true,
                    compile: function ($element, $attrs) {
                        if ($attrs[templateName] !== "") {
                            return;
                        }

                        $element.removeAttr(attrName);
                        var template = $element[0].outerHTML;

                        return function (scope, element, attrs, controllers) {
                            var controller;

                            while (!controller && controllers.length) {
                                controller = controllers.shift();
                            }

                            if (!controller) {
                                $log.warn(attrName + " without a matching parent widget found. It can be one of the following: " + parents.join(", "));
                            } else {
                                controller.template(templateName, template);
                                $element.remove();
                            }
                        };
                    }
                };
            });
        });

    })();


})(window.kendo.jQuery, window.angular);







(function ($, undefined) {

    // Imports ================================================================
    var doc = document,
        kendo = window.kendo,

        util = kendo.util,
        append = util.append,
        defined = util.defined,
        last = util.last,
        limitValue = util.limitValue,
        valueOrDefault = util.valueOrDefault,

        dataviz = kendo.dataviz,
        geom = dataviz.geometry,
        draw = dataviz.drawing,
        measureText = draw.util.measureText,
        Matrix = geom.Matrix,
        Class = kendo.Class,
        template = kendo.template,
        map = $.map,
        noop = $.noop,
        indexOf = $.inArray,
        trim = $.trim,
        math = Math,
        deepExtend = kendo.deepExtend;

    var CSS_PREFIX = "k-";

    // Constants ==============================================================
    var ANIMATION_STEP = 10,
        AXIS_LABEL_CLICK = "axisLabelClick",
        BASELINE_MARKER_SIZE = 1,
        BLACK = "#000",
        BOTTOM = "bottom",
        CENTER = "center",
        COORD_PRECISION = 3,
        CLIP = "clip",
        CIRCLE = "circle",
        CROSS = "cross",
        DEFAULT_FONT = "12px sans-serif",
        DEFAULT_HEIGHT = 400,
        DEFAULT_PRECISION = 6,
        DEFAULT_WIDTH = 600,
        DEG_TO_RAD = math.PI / 180,
        FADEIN = "fadeIn",
        FORMAT_REGEX = /\{\d+:?/,
        HEIGHT = "height",
        ID_PREFIX = "k",
        ID_POOL_SIZE = 1000,
        ID_START = 10000,
        COORDINATE_LIMIT = 100000,
        INITIAL_ANIMATION_DURATION = 600,
        INSIDE = "inside",
        LEFT = "left",
        LINEAR = "linear",
        MAX_VALUE = Number.MAX_VALUE,
        MIN_VALUE = -Number.MAX_VALUE,
        NONE = "none",
        NOTE_CLICK = "noteClick",
        NOTE_HOVER = "noteHover",
        OUTSIDE = "outside",
        RADIAL = "radial",
        RIGHT = "right",
        SWING = "swing",
        TOP = "top",
        TRIANGLE = "triangle",
        UNDEFINED = "undefined",
        UPPERCASE_REGEX = /([A-Z])/g,
        WIDTH = "width",
        WHITE = "#fff",
        X = "x",
        Y = "y",
        ZERO_THRESHOLD = 0.2;

    function getSpacing(value, defaultSpacing) {
        var spacing = { top: 0, right: 0, bottom: 0, left: 0 };

        defaultSpacing = defaultSpacing || 0;

        if (typeof (value) === "number") {
            spacing[TOP] = spacing[RIGHT] = spacing[BOTTOM] = spacing[LEFT] = value;
        } else {
            spacing[TOP] = value[TOP] || defaultSpacing;
            spacing[RIGHT] = value[RIGHT] || defaultSpacing;
            spacing[BOTTOM] = value[BOTTOM] || defaultSpacing;
            spacing[LEFT] = value[LEFT] || defaultSpacing;
        }

        return spacing;
    }

    // Geometric primitives ===================================================
    var Point2D = function (x, y) {
        var point = this;
        if (!(point instanceof Point2D)) {
            return new Point2D(x, y);
        }

        point.x = x || 0;
        point.y = y || 0;
    };

    Point2D.fn = Point2D.prototype = {
        clone: function () {
            var point = this;

            return new Point2D(point.x, point.y);
        },

        equals: function (point) {
            return point && point.x === this.x && point.y === this.y;
        },

        rotate: function (center, degrees) {
            var point = this,
                theta = degrees * DEG_TO_RAD,
                cosT = math.cos(theta),
                sinT = math.sin(theta),
                cx = center.x,
                cy = center.y,
                x = point.x,
                y = point.y;

            point.x = round(
                cx + (x - cx) * cosT + (y - cy) * sinT,
                COORD_PRECISION
            );

            point.y = round(
                cy + (y - cy) * cosT - (x - cx) * sinT,
                COORD_PRECISION
            );

            return point;
        },

        multiply: function (a) {
            var point = this;

            point.x *= a;
            point.y *= a;

            return point;
        },

        distanceTo: function (point) {
            var dx = this.x - point.x,
                dy = this.y - point.y;

            return math.sqrt(dx * dx + dy * dy);
        }
    };

    // Clock-wise, 0 points left
    Point2D.onCircle = function (c, a, r) {
        a *= DEG_TO_RAD;

        return new Point2D(
            c.x - r * math.cos(a),
            c.y - r * math.sin(a)
        );
    };

    var Box2D = function (x1, y1, x2, y2) {
        var box = this;

        if (!(box instanceof Box2D)) {
            return new Box2D(x1, y1, x2, y2);
        }

        box.x1 = x1 || 0;
        box.x2 = x2 || 0;
        box.y1 = y1 || 0;
        box.y2 = y2 || 0;
    };

    Box2D.fn = Box2D.prototype = {
        width: function () {
            return this.x2 - this.x1;
        },

        height: function () {
            return this.y2 - this.y1;
        },

        translate: function (dx, dy) {
            var box = this;

            box.x1 += dx;
            box.x2 += dx;
            box.y1 += dy;
            box.y2 += dy;

            return box;
        },

        // TODO: Accept point!
        move: function (x, y) {
            var box = this,
                height = box.height(),
                width = box.width();

            if (defined(x)) {
                box.x1 = x;
                box.x2 = box.x1 + width;
            }

            if (defined(y)) {
                box.y1 = y;
                box.y2 = box.y1 + height;
            }

            return box;
        },

        wrap: function (targetBox) {
            var box = this;

            box.x1 = math.min(box.x1, targetBox.x1);
            box.y1 = math.min(box.y1, targetBox.y1);
            box.x2 = math.max(box.x2, targetBox.x2);
            box.y2 = math.max(box.y2, targetBox.y2);

            return box;
        },

        wrapPoint: function (point) {
            this.wrap(new Box2D(point.x, point.y, point.x, point.y));

            return this;
        },

        snapTo: function (targetBox, axis) {
            var box = this;

            if (axis == X || !axis) {
                box.x1 = targetBox.x1;
                box.x2 = targetBox.x2;
            }

            if (axis == Y || !axis) {
                box.y1 = targetBox.y1;
                box.y2 = targetBox.y2;
            }

            return box;
        },

        alignTo: function (targetBox, anchor) {
            var box = this,
                height = box.height(),
                width = box.width(),
                axis = anchor == TOP || anchor == BOTTOM ? Y : X,
                offset = axis == Y ? height : width;

            if (anchor === CENTER) {
                var targetCenter = targetBox.center();
                var center = box.center();

                box.x1 += targetCenter.x - center.x;
                box.y1 += targetCenter.y - center.y;
            } else if (anchor === TOP || anchor === LEFT) {
                box[axis + 1] = targetBox[axis + 1] - offset;
            } else {
                box[axis + 1] = targetBox[axis + 2];
            }

            box.x2 = box.x1 + width;
            box.y2 = box.y1 + height;

            return box;
        },

        shrink: function (dw, dh) {
            var box = this;

            box.x2 -= dw;
            box.y2 -= dh;

            return box;
        },

        expand: function (dw, dh) {
            this.shrink(-dw, -dh);
            return this;
        },

        pad: function (padding) {
            var box = this,
                spacing = getSpacing(padding);

            box.x1 -= spacing.left;
            box.x2 += spacing.right;
            box.y1 -= spacing.top;
            box.y2 += spacing.bottom;

            return box;
        },

        unpad: function (padding) {
            var box = this,
                spacing = getSpacing(padding);

            spacing.left = -spacing.left;
            spacing.top = -spacing.top;
            spacing.right = -spacing.right;
            spacing.bottom = -spacing.bottom;

            return box.pad(spacing);
        },

        clone: function () {
            var box = this;

            return new Box2D(box.x1, box.y1, box.x2, box.y2);
        },

        center: function () {
            var box = this;

            return new Point2D(
                box.x1 + box.width() / 2,
                box.y1 + box.height() / 2
            );
        },

        containsPoint: function (point) {
            var box = this;

            return point.x >= box.x1 && point.x <= box.x2 &&
                   point.y >= box.y1 && point.y <= box.y2;
        },

        points: function () {
            var box = this;

            return [
                new Point2D(box.x1, box.y1),
                new Point2D(box.x2, box.y1),
                new Point2D(box.x2, box.y2),
                new Point2D(box.x1, box.y2)
            ];
        },

        getHash: function () {
            var box = this;

            return [box.x1, box.y1, box.x2, box.y2].join(",");
        },

        overlaps: function (box) {
            return !(box.y2 < this.y1 || this.y2 < box.y1 || box.x2 < this.x1 || this.x2 < box.x1);
        },

        rotate: function (rotation) {
            var box = this;
            var width = box.width();
            var height = box.height();
            var center = box.center();
            var cx = center.x;
            var cy = center.y;
            var r1 = rotatePoint(0, 0, cx, cy, rotation);
            var r2 = rotatePoint(width, 0, cx, cy, rotation);
            var r3 = rotatePoint(width, height, cx, cy, rotation);
            var r4 = rotatePoint(0, height, cx, cy, rotation);
            width = math.max(r1.x, r2.x, r3.x, r4.x) - math.min(r1.x, r2.x, r3.x, r4.x);
            height = math.max(r1.y, r2.y, r3.y, r4.y) - math.min(r1.y, r2.y, r3.y, r4.y);
            box.x2 = box.x1 + width;
            box.y2 = box.y1 + height;

            return box;
        },

        toRect: function () {
            return new geom.Rect([this.x1, this.y1], [this.width(), this.height()]);
        }
    };

    var Ring = Class.extend({
        init: function (center, innerRadius, radius, startAngle, angle) {
            var ring = this;

            ring.c = center;
            ring.ir = innerRadius;
            ring.r = radius;
            ring.startAngle = startAngle;
            ring.angle = angle;
        },

        clone: function () {
            var r = this;
            return new Ring(r.c, r.ir, r.r, r.startAngle, r.angle);
        },

        // TODO: Rename to midAngle
        middle: function () {
            return this.startAngle + this.angle / 2;
        },

        // TODO: Sounds like a getter
        radius: function (newRadius, innerRadius) {
            var that = this;

            if (innerRadius) {
                that.ir = newRadius;
            } else {
                that.r = newRadius;
            }

            return that;
        },

        // TODO: Remove and replace with Point2D.onCircle
        point: function (angle, innerRadius) {
            var ring = this,
                radianAngle = angle * DEG_TO_RAD,
                ax = math.cos(radianAngle),
                ay = math.sin(radianAngle),
                radius = innerRadius ? ring.ir : ring.r,
                x = round(ring.c.x - (ax * radius), COORD_PRECISION),
                y = round(ring.c.y - (ay * radius), COORD_PRECISION);

            return new Point2D(x, y);
        },

        adjacentBox: function (distance, width, height) {
            var sector = this.clone().expand(distance),
                midAndle = sector.middle(),
                midPoint = sector.point(midAndle),
                hw = width / 2,
                hh = height / 2,
                x = midPoint.x - hw,
                y = midPoint.y - hh,
                sa = math.sin(midAndle * DEG_TO_RAD),
                ca = math.cos(midAndle * DEG_TO_RAD);

            if (math.abs(sa) < 0.9) {
                x += hw * -ca / math.abs(ca);
            }

            if (math.abs(ca) < 0.9) {
                y += hh * -sa / math.abs(sa);
            }

            return new Box2D(x, y, x + width, y + height);
        },

        containsPoint: function (p) {
            var ring = this,
                c = ring.c,
                ir = ring.ir,
                r = ring.r,
                startAngle = ring.startAngle,
                endAngle = ring.startAngle + ring.angle,
                dx = p.x - c.x,
                dy = p.y - c.y,
                vector = new Point2D(dx, dy),
                startPoint = ring.point(startAngle),
                startVector = new Point2D(startPoint.x - c.x, startPoint.y - c.y),
                endPoint = ring.point(endAngle),
                endVector = new Point2D(endPoint.x - c.x, endPoint.y - c.y),
                dist = round(dx * dx + dy * dy, COORD_PRECISION);

            return (startVector.equals(vector) || clockwise(startVector, vector)) &&
                   !clockwise(endVector, vector) &&
                   dist >= ir * ir && dist <= r * r;
        },

        getBBox: function () {
            var ring = this,
                box = new Box2D(MAX_VALUE, MAX_VALUE, MIN_VALUE, MIN_VALUE),
                sa = round(ring.startAngle % 360),
                ea = round((sa + ring.angle) % 360),
                innerRadius = ring.ir,
                allAngles = [0, 90, 180, 270, sa, ea].sort(numericComparer),
                saIndex = indexOf(sa, allAngles),
                eaIndex = indexOf(ea, allAngles),
                angles,
                i,
                point;

            if (sa == ea) {
                angles = allAngles;
            } else {
                if (saIndex < eaIndex) {
                    angles = allAngles.slice(saIndex, eaIndex + 1);
                } else {
                    angles = [].concat(
                        allAngles.slice(0, eaIndex + 1),
                        allAngles.slice(saIndex, allAngles.length)
                    );
                }
            }

            for (i = 0; i < angles.length; i++) {
                point = ring.point(angles[i]);
                box.wrapPoint(point);
                box.wrapPoint(point, innerRadius);
            }

            if (!innerRadius) {
                box.wrapPoint(ring.c);
            }

            return box;
        },

        expand: function (value) {
            this.r += value;
            return this;
        }
    });

    // TODO: Remove, looks like an alias
    var Sector = Ring.extend({
        init: function (center, radius, startAngle, angle) {
            Ring.fn.init.call(this, center, 0, radius, startAngle, angle);
        },

        expand: function (value) {
            return Ring.fn.expand.call(this, value);
        },

        clone: function () {
            var sector = this;
            return new Sector(sector.c, sector.r, sector.startAngle, sector.angle);
        },

        radius: function (newRadius) {
            return Ring.fn.radius.call(this, newRadius);
        },

        point: function (angle) {
            return Ring.fn.point.call(this, angle);
        }
    });

    var ShapeBuilder = function () { };
    ShapeBuilder.fn = ShapeBuilder.prototype = {
        createRing: function (sector, options) {
            var startAngle = sector.startAngle + 180;
            var endAngle = sector.angle + startAngle;
            var center = new geom.Point(sector.c.x, sector.c.y);
            var radius = math.max(sector.r, 0);
            var innerRadius = math.max(sector.ir, 0);
            var arc = new geom.Arc(center, {
                startAngle: startAngle,
                endAngle: endAngle,
                radiusX: radius,
                radiusY: radius
            });
            var path = draw.Path.fromArc(arc, options).close();

            if (innerRadius) {
                arc.radiusX = arc.radiusY = innerRadius;
                var innerEnd = arc.pointAt(endAngle);
                path.lineTo(innerEnd.x, innerEnd.y);
                path.arc(endAngle, startAngle, innerRadius, innerRadius, true);
            } else {
                path.lineTo(center.x, center.y);
            }

            return path;
        }
    };

    ShapeBuilder.current = new ShapeBuilder();

    // View-Model primitives ==================================================
    var ChartElement = Class.extend({
        init: function (options) {
            var element = this;
            element.children = [];

            element.options = deepExtend({}, element.options, options);
        },

        reflow: function (targetBox) {
            var element = this,
                children = element.children,
                box,
                i,
                currentChild;

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

                currentChild.reflow(targetBox);
                box = box ? box.wrap(currentChild.box) : currentChild.box.clone();
            }

            element.box = box || targetBox;
        },

        destroy: function () {
            var element = this,
                children = element.children,
                root = element.getRoot(),
                i;

            if (this.animation) {
                this.animation.destroy();
            }

            for (i = 0; i < children.length; i++) {
                children[i].destroy();
            }
        },

        getRoot: function () {
            var parent = this.parent;

            return parent ? parent.getRoot() : null;
        },

        translateChildren: function (dx, dy) {
            var element = this,
                children = element.children,
                childrenCount = children.length,
                i;

            for (i = 0; i < childrenCount; i++) {
                children[i].box.translate(dx, dy);
            }
        },

        append: function () {
            append(this.children, arguments);

            for (var i = 0; i < arguments.length; i++) {
                arguments[i].parent = this;
            }
        },

        renderVisual: function () {
            if (this.options.visible === false) {
                return;
            }

            this.createVisual();

            if (this.visual) {
                this.visual.chartElement = this;

                if (this.parent) {
                    this.parent.appendVisual(this.visual);
                }
            }

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

            this.createAnimation();
            this.renderComplete();
        },

        createVisual: function () {
            this.visual = new dataviz.drawing.Group({
                zIndex: this.options.zIndex,
                visible: valueOrDefault(this.options.visible, true)
            });
        },

        createAnimation: function () {
            if (this.visual) {
                this.animation = draw.Animation.create(
                    this.visual, this.options.animation
                );
            }
        },

        appendVisual: function (childVisual) {
            if (!childVisual.chartElement) {
                childVisual.chartElement = this;
            }

            if (childVisual.options.noclip) {
                this.getRoot().visual.append(childVisual);
            } else if (defined(childVisual.options.zIndex)) {
                this.stackRoot().stackVisual(childVisual);
            } else if (this.visual) {
                this.visual.append(childVisual);
            } else {
                // Allow chart elements without visuals to
                // pass through child visuals
                this.parent.appendVisual(childVisual);
            }
        },

        stackRoot: function () {
            if (this.parent) {
                return this.parent.stackRoot();
            }

            return this;
        },

        stackVisual: function (childVisual) {
            var zIndex = childVisual.options.zIndex || 0;
            var visuals = this.visual.children;
            for (var pos = 0; pos < visuals.length; pos++) {
                var sibling = visuals[pos];
                var here = valueOrDefault(sibling.options.zIndex, 0);
                if (here > zIndex) {
                    break;
                }
            }

            this.visual.insertAt(childVisual, pos);
        },

        traverse: function (callback) {
            var children = this.children;

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

                callback(child);
                if (child.traverse) {
                    child.traverse(callback);
                }
            }
        },

        closest: function (match) {
            var element = this;
            var matched = false;

            while (element && !matched) {
                matched = match(element);

                if (!matched) {
                    element = element.parent;
                }
            }

            if (matched) {
                return element;
            }
        },

        renderComplete: $.noop,

        toggleHighlight: function (show) {
            var highlight = this._highlight;
            var options = this.options.highlight;

            if (!this.createHighlight || (options && options.visible === false)) {
                return;
            }

            if (!highlight) {
                highlight = this._highlight = this.createHighlight({
                    fill: {
                        color: WHITE,
                        opacity: 0.2
                    },
                    stroke: {
                        color: WHITE,
                        width: 1,
                        opacity: 0.2
                    }
                });

                this.appendVisual(highlight);
            }

            highlight.visible(show);
        },

        createGradientOverlay: function (element, options, gradientOptions) {
            var overlay = new draw.Path(deepExtend({
                stroke: {
                    color: NONE
                },
                fill: this.createGradient(gradientOptions),
                closed: element.options.closed
            }, options));
            overlay.segments.elements(element.segments.elements());

            return overlay;
        },

        createGradient: function (options) {
            if (this.parent) {
                return this.parent.createGradient(options);
            }
        }
    });

    var RootElement = ChartElement.extend({
        init: function (options) {
            var root = this;

            // Logical tree ID to element map
            root.modelMap = {};
            root.gradients = {};

            ChartElement.fn.init.call(root, options);
        },

        options: {
            width: DEFAULT_WIDTH,
            height: DEFAULT_HEIGHT,
            background: WHITE,
            border: {
                color: BLACK,
                width: 0
            },
            margin: getSpacing(5),
            zIndex: -2
        },

        reflow: function () {
            var root = this,
                options = root.options,
                children = root.children,
                currentBox = new Box2D(0, 0, options.width, options.height);

            root.box = currentBox.unpad(options.margin);

            for (var i = 0; i < children.length; i++) {
                children[i].reflow(currentBox);
                currentBox = boxDiff(currentBox, children[i].box);
            }
        },

        createVisual: function () {
            this.visual = new draw.Group();
            this.createBackground();
        },

        createBackground: function () {
            var options = this.options;
            var border = options.border || {};
            var box = this.box.clone().pad(options.margin).unpad(border.width);

            var background = draw.Path.fromRect(box.toRect(), {
                stroke: {
                    color: border.width ? border.color : "",
                    width: border.width,
                    dashType: border.dashType
                },
                fill: {
                    color: options.background,
                    opacity: options.opacity
                },
                zIndex: -10
            });

            this.visual.append(background);
        },

        getRoot: function () {
            return this;
        },

        createGradient: function (options) {
            var gradients = this.gradients;
            var hashCode = util.objectKey(options);
            var gradient = dataviz.Gradients[options.gradient];
            var drawingGradient;
            if (gradients[hashCode]) {
                drawingGradient = gradients[hashCode];
            } else {
                var gradientOptions = deepExtend({}, gradient, options);
                if (gradient.type == "linear") {
                    drawingGradient = new draw.LinearGradient(gradientOptions);
                } else {
                    if (options.innerRadius) {
                        gradientOptions.stops = innerRadialStops(gradientOptions);
                    }
                    drawingGradient = new draw.RadialGradient(gradientOptions);
                    drawingGradient.supportVML = gradient.supportVML !== false;
                }
                gradients[hashCode] = drawingGradient;
            }
            return drawingGradient;
        }
    });

    var BoxElement = ChartElement.extend({
        options: {
            align: LEFT,
            vAlign: TOP,
            margin: {},
            padding: {},
            border: {
                color: BLACK,
                width: 0
            },
            background: "",
            shrinkToFit: false,
            width: 0,
            height: 0,
            visible: true
        },

        reflow: function (targetBox) {
            var element = this,
                box,
                contentBox,
                options = element.options,
                width = options.width,
                height = options.height,
                hasSetSize = width && height,
                shrinkToFit = options.shrinkToFit,
                margin = getSpacing(options.margin),
                padding = getSpacing(options.padding),
                borderWidth = options.border.width,
                children = element.children,
                i, item;

            function reflowPaddingBox() {
                element.align(targetBox, X, options.align);
                element.align(targetBox, Y, options.vAlign);
                element.paddingBox = box.clone().unpad(margin).unpad(borderWidth);
            }

            contentBox = targetBox.clone();
            if (hasSetSize) {
                contentBox.x2 = contentBox.x1 + width;
                contentBox.y2 = contentBox.y1 + height;
            }

            if (shrinkToFit) {
                contentBox.unpad(margin).unpad(borderWidth).unpad(padding);
            }

            ChartElement.fn.reflow.call(element, contentBox);

            if (hasSetSize) {
                box = element.box = Box2D(0, 0, width, height);
            } else {
                box = element.box;
            }

            if (shrinkToFit && hasSetSize) {
                reflowPaddingBox();
                contentBox = element.contentBox = element.paddingBox.clone().unpad(padding);
            } else {
                contentBox = element.contentBox = box.clone();
                box.pad(padding).pad(borderWidth).pad(margin);
                reflowPaddingBox();
            }

            element.translateChildren(
                box.x1 - contentBox.x1 + margin.left + borderWidth + padding.left,
                box.y1 - contentBox.y1 + margin.top + borderWidth + padding.top);

            for (i = 0; i < children.length; i++) {
                item = children[i];
                item.reflow(item.box);
            }
        },

        align: function (targetBox, axis, alignment) {
            var element = this,
                box = element.box,
                c1 = axis + 1,
                c2 = axis + 2,
                sizeFunc = axis === X ? WIDTH : HEIGHT,
                size = box[sizeFunc]();

            if (inArray(alignment, [LEFT, TOP])) {
                box[c1] = targetBox[c1];
                box[c2] = box[c1] + size;
            } else if (inArray(alignment, [RIGHT, BOTTOM])) {
                box[c2] = targetBox[c2];
                box[c1] = box[c2] - size;
            } else if (alignment == CENTER) {
                box[c1] = targetBox[c1] + (targetBox[sizeFunc]() - size) / 2;
                box[c2] = box[c1] + size;
            }
        },

        hasBox: function () {
            var options = this.options;
            return options.border.width || options.background;
        },

        createVisual: function () {
            ChartElement.fn.createVisual.call(this);

            var options = this.options;
            if (options.visible && this.hasBox()) {
                this.visual.append(draw.Path.fromRect(
                    this.paddingBox.toRect(), this.visualStyle()
                ));
            }
        },

        visualStyle: function () {
            var boxElement = this,
                options = boxElement.options,
                border = options.border || {};

            return {
                stroke: {
                    width: border.width,
                    color: border.color,
                    opacity: valueOrDefault(border.opacity, options.opacity),
                    dashType: border.dashType
                },
                fill: {
                    color: options.background,
                    opacity: options.opacity
                },
                cursor: options.cursor
            };
        }
    });

    var Text = ChartElement.extend({
        init: function (content, options) {
            var text = this;

            ChartElement.fn.init.call(text, options);

            text.content = content;

            // Calculate size
            text.reflow(Box2D());
        },

        options: {
            font: DEFAULT_FONT,
            color: BLACK,
            align: LEFT,
            vAlign: ""
        },

        reflow: function (targetBox) {
            var text = this,
                options = text.options,
                size,
                margin;

            size = options.size =
                measureText(text.content, { font: options.font });

            text.baseline = size.baseline;

            text.box = Box2D(targetBox.x1, targetBox.y1,
                    targetBox.x1 + size.width, targetBox.y1 + size.height);
        },

        createVisual: function () {
            var opt = this.options;

            this.visual = new draw.Text(this.content, this.box.toRect().topLeft(), {
                font: opt.font,
                fill: { color: opt.color, opacity: opt.opacity },
                cursor: opt.cursor
            });
        }
    });

    var FloatElement = ChartElement.extend({
        init: function (options) {
            ChartElement.fn.init.call(this, options);
            this._initDirection();
        },

        _initDirection: function () {
            var options = this.options;
            if (options.vertical) {
                this.groupAxis = X;
                this.elementAxis = Y;
                this.groupSizeField = WIDTH;
                this.elementSizeField = HEIGHT;
                this.groupSpacing = options.spacing;
                this.elementSpacing = options.vSpacing;
            } else {
                this.groupAxis = Y;
                this.elementAxis = X;
                this.groupSizeField = HEIGHT;
                this.elementSizeField = WIDTH;
                this.groupSpacing = options.vSpacing;
                this.elementSpacing = options.spacing;
            }
        },

        options: {
            vertical: true,
            wrap: true,
            vSpacing: 0,
            spacing: 0
        },

        reflow: function (targetBox) {
            this.box = targetBox.clone();
            this.reflowChildren();
        },

        reflowChildren: function () {
            var floatElement = this;
            var box = floatElement.box;
            var options = floatElement.options;
            var elementSpacing = floatElement.elementSpacing;
            var groupSpacing = floatElement.groupSpacing;

            var elementAxis = floatElement.elementAxis;
            var groupAxis = floatElement.groupAxis;
            var elementSizeField = floatElement.elementSizeField;
            var groupSizeField = floatElement.groupSizeField;

            var groupOptions = floatElement.groupOptions();
            var groups = groupOptions.groups;
            var groupsCount = groups.length;

            var groupsStart = box[groupAxis + 1] +
                floatElement.alignStart(groupOptions.groupsSize, box[groupSizeField]());

            var groupStart = groupsStart;
            var elementStart;
            var groupElementStart;

            var group;
            var groupElements;
            var groupElementsCount;

            var idx;
            var groupIdx;

            var element;
            var elementBox;
            var elementSize;

            if (groupsCount) {
                for (groupIdx = 0; groupIdx < groupsCount; groupIdx++) {
                    group = groups[groupIdx];
                    groupElements = group.groupElements;
                    groupElementsCount = groupElements.length;
                    elementStart = box[elementAxis + 1];
                    for (idx = 0; idx < groupElementsCount; idx++) {
                        element = groupElements[idx];
                        elementSize = floatElement.elementSize(element);
                        groupElementStart = groupStart +
                            floatElement.alignStart(elementSize[groupSizeField], group.groupSize);

                        elementBox = Box2D();
                        elementBox[groupAxis + 1] = groupElementStart;
                        elementBox[groupAxis + 2] = groupElementStart + elementSize[groupSizeField];
                        elementBox[elementAxis + 1] = elementStart;
                        elementBox[elementAxis + 2] = elementStart + elementSize[elementSizeField];

                        element.reflow(elementBox);

                        elementStart += elementSize[elementSizeField] + floatElement.elementSpacing;
                    }
                    groupStart += group.groupSize + floatElement.groupSpacing;
                }
                box[groupAxis + 1] = groupsStart;
                box[groupAxis + 2] = groupsStart + groupOptions.groupsSize;
                box[elementAxis + 2] = box[elementAxis + 1] + groupOptions.maxGroupElementsSize;
            }
        },

        alignStart: function (size, maxSize) {
            var start = 0;
            var align = this.options.align;
            if (align == RIGHT || align == BOTTOM) {
                start = maxSize - size;
            } else if (align == CENTER) {
                start = (maxSize - size) / 2;
            }
            return start;
        },

        groupOptions: function () {
            var floatElement = this;
            var box = floatElement.box;
            var children = floatElement.children;
            var childrenCount = children.length;
            var elementSizeField = this.elementSizeField;
            var groupSizeField = this.groupSizeField;
            var elementSpacing = this.elementSpacing;
            var groupSpacing = this.groupSpacing;
            var maxSize = round(box[elementSizeField]());
            var idx = 0;
            var groupSize = 0;
            var elementSize;
            var element;
            var groupElementsSize = 0;
            var groupsSize = 0;
            var groups = [];
            var groupElements = [];
            var maxGroupElementsSize = 0;

            for (idx = 0; idx < childrenCount; idx++) {
                element = children[idx];
                if (!element.box) {
                    element.reflow(box);
                }

                elementSize = this.elementSize(element);
                if (floatElement.options.wrap && round(groupElementsSize + elementSpacing + elementSize[elementSizeField]) > maxSize) {
                    groups.push({
                        groupElements: groupElements,
                        groupSize: groupSize,
                        groupElementsSize: groupElementsSize
                    });
                    maxGroupElementsSize = math.max(maxGroupElementsSize, groupElementsSize);
                    groupsSize += groupSpacing + groupSize;
                    groupSize = 0;
                    groupElementsSize = 0;
                    groupElements = [];
                }
                groupSize = math.max(groupSize, elementSize[groupSizeField]);
                if (groupElementsSize > 0) {
                    groupElementsSize += elementSpacing;
                }
                groupElementsSize += elementSize[elementSizeField];
                groupElements.push(element);
            }

            groups.push({
                groupElements: groupElements,
                groupSize: groupSize,
                groupElementsSize: groupElementsSize
            });
            maxGroupElementsSize = math.max(maxGroupElementsSize, groupElementsSize);
            groupsSize += groupSize;

            return {
                groups: groups,
                groupsSize: groupsSize,
                maxGroupElementsSize: maxGroupElementsSize
            };
        },

        elementSize: function (element) {
            return {
                width: element.box.width(),
                height: element.box.height()
            };
        },

        createVisual: noop
    });

    var TextBox = BoxElement.extend({
        ROWS_SPLIT_REGEX: /\n|\\n/m,

        init: function (content, options) {
            var textbox = this;
            textbox.content = content;

            BoxElement.fn.init.call(textbox, options);

            textbox._initContainer();

            textbox.reflow(Box2D());
        },

        _initContainer: function () {
            var textbox = this;
            var options = textbox.options;
            var rows = (textbox.content + "").split(textbox.ROWS_SPLIT_REGEX);
            var floatElement = new FloatElement({ vertical: true, align: options.align, wrap: false });
            var textOptions = deepExtend({}, options, { opacity: 1, animation: null });
            var hasBox = textbox.hasBox();
            var text;
            var rowIdx;

            textbox.container = floatElement;
            textbox.append(floatElement);

            for (rowIdx = 0; rowIdx < rows.length; rowIdx++) {
                text = new Text(trim(rows[rowIdx]), textOptions);
                floatElement.append(text);
            }
        },

        reflow: function (targetBox) {
            var textbox = this;
            var options = textbox.options;
            var align = options.align;
            var rotation = options.rotation;
            textbox.container.options.align = align;
            BoxElement.fn.reflow.call(textbox, targetBox);
            if (rotation) {
                var margin = options.margin;
                var box = textbox.box.unpad(margin);
                textbox.normalBox = box.clone();
                box.rotate(rotation);
                box.pad(margin);
                textbox.align(targetBox, X, align);
                textbox.align(targetBox, Y, options.vAlign);
            }
        },

        createVisual: function () {
            var options = this.options;

            if (!options.visible) {
                return;
            }

            this.visual = new dataviz.drawing.Group({
                transform: this.rotationTransform(),
                zIndex: options.zIndex,
                noclip: options.noclip
            });

            if (this.hasBox()) {
                var box = draw.Path.fromRect(this.paddingBox.toRect(), this.visualStyle());
                this.visual.append(box);
            }
        },

        rotationTransform: function () {
            var rotation = this.options.rotation;
            if (!rotation) {
                return null;
            }

            var center = this.normalBox.center();
            var cx = center.x;
            var cy = center.y;
            var boxCenter = this.box.center();

            return geom.transform()
                       .translate(boxCenter.x - cx, boxCenter.y - cy)
                       .rotate(rotation, [cx, cy]);
        },

        rotationMatrix: function () {
            var textbox = this;
            var options = textbox.options;
            var normalBox = textbox.normalBox;
            var center = normalBox.center();
            var cx = center.x;
            var cy = center.y;
            var boxCenter = textbox.box.center();
            var offsetX = boxCenter.x - cx;
            var offsetY = boxCenter.y - cy;
            var matrix = Matrix.translate(offsetX, offsetY)
                    .times(Matrix.rotate(options.rotation, cx, cy));

            return matrix;
        }
    });

    var Title = ChartElement.extend({
        init: function (options) {
            var title = this;
            ChartElement.fn.init.call(title, options);

            options = title.options;
            title.append(
                new TextBox(options.text, deepExtend({}, options, {
                    vAlign: options.position
                }))
            );
        },

        options: {
            color: BLACK,
            position: TOP,
            align: CENTER,
            margin: getSpacing(5),
            padding: getSpacing(5)
        },

        reflow: function (targetBox) {
            var title = this;

            ChartElement.fn.reflow.call(title, targetBox);
            title.box.snapTo(targetBox, X);
        }
    });

    Title.buildTitle = function (options, parent, defaultOptions) {
        var title;

        if (typeof options === "string") {
            options = { text: options };
        }

        options = deepExtend({ visible: true }, defaultOptions, options);

        if (options && options.visible && options.text) {
            title = new Title(options);
            parent.append(title);
        }

        return title;
    };

    var AxisLabel = TextBox.extend({
        init: function (value, text, index, dataItem, options) {
            var label = this;

            label.text = text;
            label.value = value;
            label.index = index;
            label.dataItem = dataItem;

            TextBox.fn.init.call(label, text, options);
        },

        click: function (widget, e) {
            var label = this;

            widget.trigger(AXIS_LABEL_CLICK, {
                element: $(e.target),
                value: label.value,
                text: label.text,
                index: label.index,
                dataItem: label.dataItem,
                axis: label.parent.options
            });
        }
    });

    function createAxisTick(options, tickOptions) {
        var tickX = options.tickX,
            tickY = options.tickY,
            position = options.position;

        var tick = new draw.Path({
            stroke: {
                width: tickOptions.width,
                color: tickOptions.color
            }
        });

        if (options.vertical) {
            tick.moveTo(tickX, position)
                .lineTo(tickX + tickOptions.size, position);
        } else {
            tick.moveTo(position, tickY)
                .lineTo(position, tickY + tickOptions.size);
        }

        alignPathToPixel(tick);

        return tick;
    }

    function createAxisGridLine(options, gridLine) {
        var lineStart = options.lineStart,
            lineEnd = options.lineEnd,
            position = options.position,
            start, end;

        var line = new draw.Path({
            stroke: {
                width: gridLine.width,
                color: gridLine.color,
                dashType: gridLine.dashType
            }
        });

        if (options.vertical) {
            line.moveTo(lineStart, position)
                .lineTo(lineEnd, position);
        } else {
            line.moveTo(position, lineStart)
                .lineTo(position, lineEnd);
        }

        alignPathToPixel(line);

        return line;
    }

    var Axis = ChartElement.extend({
        init: function (options) {
            var axis = this;

            ChartElement.fn.init.call(axis, options);

            if (!axis.options.visible) {
                axis.options = deepExtend({}, axis.options, {
                    labels: {
                        visible: false
                    },
                    line: {
                        visible: false
                    },
                    margin: 0,
                    majorTickSize: 0,
                    minorTickSize: 0
                });
            }

            axis.options.minorTicks = deepExtend({}, {
                color: axis.options.line.color,
                width: axis.options.line.width,
                visible: axis.options.minorTickType != NONE
            }, axis.options.minorTicks, {
                size: axis.options.minorTickSize,
                align: axis.options.minorTickType
            });

            axis.options.majorTicks = deepExtend({}, {
                color: axis.options.line.color,
                width: axis.options.line.width,
                visible: axis.options.majorTickType != NONE
            }, axis.options.majorTicks, {
                size: axis.options.majorTickSize,
                align: axis.options.majorTickType
            });

            axis.createLabels();
            axis.createTitle();
            axis.createNotes();
        },

        options: {
            labels: {
                visible: true,
                rotation: 0,
                mirror: false,
                step: 1,
                skip: 0
            },
            line: {
                width: 1,
                color: BLACK,
                visible: true
            },
            title: {
                visible: true,
                position: CENTER
            },
            majorTicks: {
                align: OUTSIDE,
                size: 4,
                skip: 0,
                step: 1
            },
            minorTicks: {
                align: OUTSIDE,
                size: 3,
                skip: 0,
                step: 1
            },
            axisCrossingValue: 0,
            majorTickType: OUTSIDE,
            minorTickType: NONE,
            majorGridLines: {
                skip: 0,
                step: 1
            },
            minorGridLines: {
                visible: false,
                width: 1,
                color: BLACK,
                skip: 0,
                step: 1
            },
            // TODO: Move to line or labels options
            margin: 5,
            visible: true,
            reverse: false,
            justified: true,
            notes: {
                label: {
                    text: ""
                }
            },

            _alignLines: true
        },

        // abstract labelsCount(): Number
        // abstract createAxisLabel(index, options): AxisLabel

        createLabels: function () {
            var axis = this,
                options = axis.options,
                align = options.vertical ? RIGHT : CENTER,
                labelOptions = deepExtend({}, options.labels, {
                    align: align,
                    zIndex: options.zIndex
                }),
                step = labelOptions.step;

            axis.labels = [];

            if (labelOptions.visible) {
                var labelsCount = axis.labelsCount(),
                    label,
                    i;

                for (i = labelOptions.skip; i < labelsCount; i += step) {
                    label = axis.createAxisLabel(i, labelOptions);
                    if (label) {
                        axis.append(label);
                        axis.labels.push(label);
                    }
                }
            }
        },

        lineBox: function () {
            var axis = this,
                options = axis.options,
                box = axis.box,
                vertical = options.vertical,
                labels = axis.labels,
                labelSize = vertical ? HEIGHT : WIDTH,
                justified = options.justified,
                mirror = options.labels.mirror,
                axisX = mirror ? box.x1 : box.x2,
                axisY = mirror ? box.y2 : box.y1,
                startMargin = 0,
                endMargin = options.line.width;

            if (justified && labels.length > 1) {
                startMargin = labels[0].box[labelSize]() / 2;
                endMargin = last(labels).box[labelSize]() / 2;
            }

            return vertical ?
                Box2D(axisX, box.y1 + startMargin, axisX, box.y2 - endMargin) :
                Box2D(box.x1 + startMargin, axisY, box.x2 - endMargin, axisY);
        },

        createTitle: function () {
            var axis = this,
                options = axis.options,
                titleOptions = deepExtend({
                    rotation: options.vertical ? -90 : 0,
                    text: "",
                    zIndex: 1
                }, options.title),
                title;

            if (titleOptions.visible && titleOptions.text) {
                title = new TextBox(titleOptions.text, titleOptions);
                axis.append(title);
                axis.title = title;
            }
        },

        createNotes: function () {
            var axis = this,
                options = axis.options,
                notes = options.notes,
                items = notes.data || [],
                noteTemplate, i, text, item, note;

            axis.notes = [];

            for (i = 0; i < items.length; i++) {
                item = deepExtend({}, notes, items[i]);
                item.value = axis.parseNoteValue(item.value);

                note = new Note(item.value, item.label.text, null, null, null, item);

                if (note.options.visible) {
                    if (defined(note.options.position)) {
                        if (options.vertical && !inArray(note.options.position, [LEFT, RIGHT])) {
                            note.options.position = options.reverse ? LEFT : RIGHT;
                        } else if (!options.vertical && !inArray(note.options.position, [TOP, BOTTOM])) {
                            note.options.position = options.reverse ? BOTTOM : TOP;
                        }
                    } else {
                        if (options.vertical) {
                            note.options.position = options.reverse ? LEFT : RIGHT;
                        } else {
                            note.options.position = options.reverse ? BOTTOM : TOP;
                        }
                    }
                    axis.append(note);
                    axis.notes.push(note);
                }
            }
        },

        parseNoteValue: function (value) {
            return value;
        },

        createVisual: function () {
            ChartElement.fn.createVisual.call(this);

            this.createPlotBands();
            this.createBackground();
            this.createLine();
        },

        gridLinesVisual: function () {
            var gridLines = this._gridLines;
            if (!gridLines) {
                gridLines = this._gridLines = new draw.Group({
                    zIndex: -2
                });
                this.appendVisual(this._gridLines);
            }

            return gridLines;
        },

        createTicks: function (lineGroup) {
            var axis = this,
                options = axis.options,
                lineBox = axis.lineBox(),
                mirror = options.labels.mirror,
                majorUnit = options.majorTicks.visible ? options.majorUnit : 0,
                tickLineOptions = {
                    // TODO
                    // _alignLines: options._alignLines,
                    vertical: options.vertical
                },
                start, end;

            function render(tickPositions, tickOptions, skipUnit) {
                var i, count = tickPositions.length;

                if (tickOptions.visible) {
                    for (i = tickOptions.skip; i < count; i += tickOptions.step) {
                        if (defined(skipUnit) && (i % skipUnit === 0)) {
                            continue;
                        }

                        tickLineOptions.tickX = mirror ? lineBox.x2 : lineBox.x2 - tickOptions.size;
                        tickLineOptions.tickY = mirror ? lineBox.y1 - tickOptions.size : lineBox.y1;
                        tickLineOptions.position = tickPositions[i];

                        lineGroup.append(createAxisTick(tickLineOptions, tickOptions));
                    }
                }
            }

            render(axis.getMajorTickPositions(), options.majorTicks);
            render(axis.getMinorTickPositions(), options.minorTicks, majorUnit / options.minorUnit);
        },

        createLine: function () {
            var axis = this,
                options = axis.options,
                line = options.line,
                lineBox = axis.lineBox(),
                lineOptions;

            if (line.width > 0 && line.visible) {
                var path = new draw.Path({
                    stroke: {
                        width: line.width,
                        color: line.color,
                        dashType: line.dashType
                    }

                    /* TODO
                    zIndex: line.zIndex,
                    */
                });

                path.moveTo(lineBox.x1, lineBox.y1)
                    .lineTo(lineBox.x2, lineBox.y2);

                if (options._alignLines) {
                    alignPathToPixel(path);
                }

                var group = this._lineGroup = new draw.Group();
                group.append(path);

                this.visual.append(group);
                this.createTicks(group);
            }
        },

        getActualTickSize: function () {
            var axis = this,
                options = axis.options,
                tickSize = 0;

            if (options.majorTicks.visible && options.minorTicks.visible) {
                tickSize = math.max(options.majorTicks.size, options.minorTicks.size);
            } else if (options.majorTicks.visible) {
                tickSize = options.majorTicks.size;
            } else if (options.minorTicks.visible) {
                tickSize = options.minorTicks.size;
            }

            return tickSize;
        },

        createBackground: function () {
            var axis = this,
                options = axis.options,
                background = options.background,
                box = axis.box;

            if (background) {
                axis._backgroundPath = draw.Path.fromRect(box.toRect(), {
                    fill: {
                        color: background
                    },
                    stroke: null
                });

                this.visual.append(axis._backgroundPath);
            }
        },

        createPlotBands: function () {
            var axis = this,
                options = axis.options,
                plotBands = options.plotBands || [],
                vertical = options.vertical,
                plotArea = axis.plotArea,
                slotX, slotY, from, to;

            if (plotBands.length === 0) {
                return;
            }

            var group = this._plotbandGroup = new draw.Group({
                zIndex: -1
            });

            var range = this.range();
            $.each(plotBands, function (i, item) {
                from = valueOrDefault(item.from, MIN_VALUE);
                to = valueOrDefault(item.to, MAX_VALUE);
                var element = [];

                if (isInRange(from, range) || isInRange(to, range)) {
                    if (vertical) {
                        slotX = plotArea.axisX.lineBox();
                        slotY = axis.getSlot(item.from, item.to, true);
                    } else {
                        slotX = axis.getSlot(item.from, item.to, true);
                        slotY = plotArea.axisY.lineBox();
                    }

                    var bandRect = new geom.Rect(
                        [slotX.x1, slotY.y1],
                        [slotX.width(), slotY.height()]
                    );

                    var path = draw.Path.fromRect(bandRect, {
                        fill: {
                            color: item.color,
                            opacity: item.opacity
                        },
                        stroke: null
                    });

                    group.append(path);
                }
            });

            axis.appendVisual(group);
        },

        createGridLines: function (altAxis) {
            var axis = this,
                options = axis.options,
                axisLineVisible = altAxis.options.line.visible,
                majorGridLines = options.majorGridLines,
                majorUnit = majorGridLines.visible ? options.majorUnit : 0,
                vertical = options.vertical,
                lineBox = altAxis.lineBox(),
                linePos = lineBox[vertical ? "y1" : "x1"],
                lineOptions = {
                    lineStart: lineBox[vertical ? "x1" : "y1"],
                    lineEnd: lineBox[vertical ? "x2" : "y2"],
                    vertical: vertical
                },
                pos, majorTicks = [];

            var container = this.gridLinesVisual();
            function render(tickPositions, gridLine, skipUnit) {
                var count = tickPositions.length,
                    i;

                if (gridLine.visible) {
                    for (i = gridLine.skip; i < count; i += gridLine.step) {
                        pos = round(tickPositions[i]);
                        if (!inArray(pos, majorTicks)) {
                            if (i % skipUnit !== 0 && (!axisLineVisible || linePos !== pos)) {
                                lineOptions.position = pos;
                                container.append(createAxisGridLine(lineOptions, gridLine));

                                majorTicks.push(pos);
                            }
                        }
                    }
                }
            }

            render(axis.getMajorTickPositions(), options.majorGridLines);
            render(axis.getMinorTickPositions(), options.minorGridLines, majorUnit / options.minorUnit);

            return container.children;
        },

        reflow: function (box) {
            var axis = this,
                options = axis.options,
                vertical = options.vertical,
                labels = axis.labels,
                count = labels.length,
                space = axis.getActualTickSize() + options.margin,
                maxLabelHeight = 0,
                maxLabelWidth = 0,
                title = axis.title,
                label, i;

            for (i = 0; i < count; i++) {
                label = labels[i];
                maxLabelHeight = math.max(maxLabelHeight, label.box.height());
                maxLabelWidth = math.max(maxLabelWidth, label.box.width());
            }

            if (title) {
                if (vertical) {
                    maxLabelWidth += title.box.width();
                } else {
                    maxLabelHeight += title.box.height();
                }
            }

            if (vertical) {
                axis.box = Box2D(
                    box.x1, box.y1,
                    box.x1 + maxLabelWidth + space, box.y2
                );
            } else {
                axis.box = Box2D(
                    box.x1, box.y1,
                    box.x2, box.y1 + maxLabelHeight + space
                );
            }

            axis.arrangeTitle();
            axis.arrangeLabels();
            axis.arrangeNotes();
        },

        arrangeLabels: function () {
            var axis = this,
                options = axis.options,
                labels = axis.labels,
                labelsBetweenTicks = !options.justified,
                vertical = options.vertical,
                lineBox = axis.lineBox(),
                mirror = options.labels.mirror,
                tickPositions = axis.getMajorTickPositions(),
                labelOffset = axis.getActualTickSize() + options.margin,
                labelBox, labelY, i;

            for (i = 0; i < labels.length; i++) {
                var label = labels[i],
                    tickIx = label.index,
                    labelSize = vertical ? label.box.height() : label.box.width(),
                    labelPos = tickPositions[tickIx] - (labelSize / 2),
                    firstTickPosition, nextTickPosition, middle, labelX;

                if (vertical) {
                    if (labelsBetweenTicks) {
                        firstTickPosition = tickPositions[tickIx];
                        nextTickPosition = tickPositions[tickIx + 1];

                        middle = firstTickPosition + (nextTickPosition - firstTickPosition) / 2;
                        labelPos = middle - (labelSize / 2);
                    }

                    labelX = lineBox.x2;

                    if (mirror) {
                        labelX += labelOffset;
                    } else {
                        labelX -= labelOffset + label.box.width();
                    }

                    labelBox = label.box.move(labelX, labelPos);
                } else {
                    if (labelsBetweenTicks) {
                        firstTickPosition = tickPositions[tickIx];
                        nextTickPosition = tickPositions[tickIx + 1];
                    } else {
                        firstTickPosition = labelPos;
                        nextTickPosition = labelPos + labelSize;
                    }

                    labelY = lineBox.y1;

                    if (mirror) {
                        labelY -= labelOffset + label.box.height();
                    } else {
                        labelY += labelOffset;
                    }

                    labelBox = Box2D(firstTickPosition, labelY,
                                    nextTickPosition, labelY + label.box.height());
                }

                label.reflow(labelBox);
            }
        },

        arrangeTitle: function () {
            var axis = this,
                options = axis.options,
                mirror = options.labels.mirror,
                vertical = options.vertical,
                title = axis.title;

            if (title) {
                if (vertical) {
                    title.options.align = mirror ? RIGHT : LEFT;
                    title.options.vAlign = title.options.position;
                } else {
                    title.options.align = title.options.position;
                    title.options.vAlign = mirror ? TOP : BOTTOM;
                }

                title.reflow(axis.box);
            }
        },

        arrangeNotes: function () {
            var axis = this,
                i, item, slot, value;

            for (i = 0; i < axis.notes.length; i++) {
                item = axis.notes[i];
                value = item.options.value;
                if (defined(value)) {
                    if (axis.shouldRenderNote(value)) {
                        item.show();
                    } else {
                        item.hide();
                    }

                    slot = axis.getSlot(value);
                } else {
                    item.hide();
                }

                item.reflow(slot || axis.lineBox());
            }
        },

        alignTo: function (secondAxis) {
            var axis = this,
                lineBox = secondAxis.lineBox(),
                vertical = axis.options.vertical,
                pos = vertical ? Y : X;

            axis.box.snapTo(lineBox, pos);
            if (vertical) {
                axis.box.shrink(0, axis.lineBox().height() - lineBox.height());
            } else {
                axis.box.shrink(axis.lineBox().width() - lineBox.width(), 0);
            }
            axis.box[pos + 1] -= axis.lineBox()[pos + 1] - lineBox[pos + 1];
            axis.box[pos + 2] -= axis.lineBox()[pos + 2] - lineBox[pos + 2];
        },

        axisLabelText: function (value, dataItem, options) {
            var text = value;

            if (options.template) {
                var tmpl = template(options.template);
                text = tmpl({ value: value, dataItem: dataItem, format: options.format, culture: options.culture });
            } else if (options.format) {
                if (options.format.match(FORMAT_REGEX)) {
                    text = kendo.format(options.format, value);
                } else {
                    text = kendo.toString(value, options.format, options.culture);
                }
            }

            return text;
        }
    });

    var Note = BoxElement.extend({
        init: function (value, text, dataItem, category, series, options) {
            var note = this;

            BoxElement.fn.init.call(note, options);
            note.value = value;
            note.text = text;
            note.dataItem = dataItem;
            note.category = category;
            note.series = series;

            note.render();
        },

        options: {
            icon: {
                visible: true,
                type: CIRCLE
            },
            label: {
                position: INSIDE,
                visible: true,
                align: CENTER,
                vAlign: CENTER
            },
            line: {
                visible: true
            },
            visible: true,
            position: TOP,
            zIndex: 2
        },

        hide: function () {
            this.options.visible = false;
        },

        show: function () {
            this.options.visible = true;
        },

        render: function () {
            var note = this,
                options = note.options,
                label = options.label,
                text = note.text,
                icon = options.icon,
                size = icon.size,
                box = Box2D(),
                marker, width, height, noteTemplate;

            if (options.visible) {
                if (defined(label) && label.visible) {
                    if (label.template) {
                        noteTemplate = template(label.template);
                        text = noteTemplate({
                            dataItem: note.dataItem,
                            category: note.category,
                            value: note.value,
                            text: text,
                            series: note.series
                        });
                    } else if (label.format) {
                        text = autoFormat(label.format, text);
                    }

                    note.label = new TextBox(text, deepExtend({}, label));
                    note.append(note.label);

                    if (label.position === INSIDE) {
                        if (icon.type === CIRCLE) {
                            size = math.max(note.label.box.width(), note.label.box.height());
                        } else {
                            width = note.label.box.width();
                            height = note.label.box.height();
                        }
                        box.wrap(note.label.box);
                    }
                }

                icon.width = width || size;
                icon.height = height || size;

                marker = new ShapeElement(deepExtend({}, icon));

                note.marker = marker;
                note.append(marker);
                marker.reflow(Box2D());
                note.wrapperBox = box.wrap(marker.box);
            }
        },

        reflow: function (targetBox) {
            var note = this,
                options = note.options,
                center = targetBox.center(),
                wrapperBox = note.wrapperBox,
                length = options.line.length,
                position = options.position,
                label = note.label,
                marker = note.marker,
                lineStart, box, contentBox;

            // TODO: Review
            if (options.visible) {
                if (inArray(position, [LEFT, RIGHT])) {
                    if (position === LEFT) {
                        contentBox = wrapperBox.alignTo(targetBox, position).translate(-length, targetBox.center().y - wrapperBox.center().y);

                        if (options.line.visible) {
                            lineStart = [math.floor(targetBox.x1), center.y];
                            note.linePoints = [
                                lineStart,
                                [math.floor(contentBox.x2), center.y]
                            ];
                            box = contentBox.clone().wrapPoint(lineStart);
                        }
                    } else {
                        contentBox = wrapperBox.alignTo(targetBox, position).translate(length, targetBox.center().y - wrapperBox.center().y);

                        if (options.line.visible) {
                            lineStart = [math.floor(targetBox.x2), center.y];
                            note.linePoints = [
                                lineStart,
                                [math.floor(contentBox.x1), center.y]
                            ];
                            box = contentBox.clone().wrapPoint(lineStart);
                        }
                    }
                } else {
                    if (position === BOTTOM) {
                        contentBox = wrapperBox.alignTo(targetBox, position).translate(targetBox.center().x - wrapperBox.center().x, length);

                        if (options.line.visible) {
                            lineStart = [math.floor(center.x), math.floor(targetBox.y2)];
                            note.linePoints = [
                                lineStart,
                                [math.floor(center.x), math.floor(contentBox.y1)]
                            ];
                            box = contentBox.clone().wrapPoint(lineStart);
                        }
                    } else {
                        contentBox = wrapperBox.alignTo(targetBox, position).translate(targetBox.center().x - wrapperBox.center().x, -length);

                        if (options.line.visible) {
                            lineStart = [math.floor(center.x), math.floor(targetBox.y1)];
                            note.linePoints = [
                                lineStart,
                                [math.floor(center.x), math.floor(contentBox.y2)]
                            ];
                            box = contentBox.clone().wrapPoint(lineStart);
                        }
                    }
                }

                if (marker) {
                    marker.reflow(contentBox);
                }

                if (label) {
                    label.reflow(contentBox);
                    if (marker) {
                        if (options.label.position === OUTSIDE) {
                            label.box.alignTo(marker.box, position);
                        }
                        label.reflow(label.box);
                    }
                }
                note.contentBox = contentBox;
                note.box = box || contentBox;
            }
        },

        createVisual: function () {
            BoxElement.fn.createVisual.call(this);

            if (this.options.visible) {
                this.createLine();
            }
        },

        createLine: function () {
            var options = this.options.line;

            var path = draw.Path.fromPoints(this.linePoints, {
                stroke: {
                    color: options.color,
                    width: options.width,
                    dashType: options.dashType
                }
            });

            this.visual.append(path);
        },

        click: function (widget, e) {
            var args = this.eventArgs(e);

            if (!widget.trigger(NOTE_CLICK, args)) {
                e.preventDefault();
            }
        },

        hover: function (widget, e) {
            var args = this.eventArgs(e);

            if (!widget.trigger(NOTE_HOVER, args)) {
                e.preventDefault();
            }
        },

        leave: function (widget) {
            widget._unsetActivePoint();
        },

        eventArgs: function (e) {
            var note = this,
                options = note.options;

            return {
                element: $(e.target),
                text: defined(options.label) ? options.label.text : "",
                dataItem: note.dataItem,
                series: note.series,
                value: note.value,
                category: note.category
            };
        }
    });

    var ShapeElement = BoxElement.extend({
        options: {
            type: CIRCLE,
            align: CENTER,
            vAlign: CENTER
        },

        getElement: function () {
            var marker = this,
                options = marker.options,
                type = options.type,
                rotation = options.rotation,
                box = marker.paddingBox,
                element,
                center = box.center(),
                halfWidth = box.width() / 2,
                points,
                i;

            if (!options.visible || !marker.hasBox()) {
                return;
            }

            var style = marker.visualStyle();

            if (type === CIRCLE) {
                element = new draw.Circle(
                    new geom.Circle([
                        round(box.x1 + halfWidth, COORD_PRECISION),
                        round(box.y1 + box.height() / 2, COORD_PRECISION)
                    ], halfWidth),
                    style
                );
            } else if (type === TRIANGLE) {
                element = draw.Path.fromPoints([
                    [box.x1 + halfWidth, box.y1],
                    [box.x1, box.y2],
                    [box.x2, box.y2]
                ], style).close();
            } else if (type === CROSS) {
                element = new draw.MultiPath(style);

                element.moveTo(box.x1, box.y1).lineTo(box.x2, box.y2);
                element.moveTo(box.x1, box.y2).lineTo(box.x2, box.y1);
            } else {
                element = draw.Path.fromRect(box.toRect(), style);
            }

            if (rotation) {
                element.transform(geom.transform()
                    .rotate(-rotation, [center.x, center.y])
                );
            }

            element.options.zIndex = this.options.zIndex;
            return element;
        },

        createVisual: function () {
            this.visual = this.getElement();
        }
    });

    var NumericAxis = Axis.extend({
        init: function (seriesMin, seriesMax, options) {
            var axis = this,
                defaultOptions = axis.initDefaults(seriesMin, seriesMax, options);

            Axis.fn.init.call(axis, defaultOptions);
        },

        startValue: function () {
            return 0;
        },

        options: {
            type: "numeric",
            min: 0,
            max: 1,
            vertical: true,
            majorGridLines: {
                visible: true,
                width: 1,
                color: BLACK
            },
            zIndex: 1
        },

        initDefaults: function (seriesMin, seriesMax, options) {
            var axis = this,
                narrowRange = options.narrowRange,
                autoMin = axis.autoAxisMin(seriesMin, seriesMax, narrowRange),
                autoMax = axis.autoAxisMax(seriesMin, seriesMax, narrowRange),
                majorUnit = autoMajorUnit(autoMin, autoMax),
                autoOptions = {
                    majorUnit: majorUnit
                },
                userSetLimits;

            if (options.roundToMajorUnit !== false) {
                if (autoMin < 0 && remainderClose(autoMin, majorUnit, 1 / 3)) {
                    autoMin -= majorUnit;
                }

                if (autoMax > 0 && remainderClose(autoMax, majorUnit, 1 / 3)) {
                    autoMax += majorUnit;
                }
            }

            autoOptions.min = floor(autoMin, majorUnit);
            autoOptions.max = ceil(autoMax, majorUnit);

            if (options) {
                userSetLimits = defined(options.min) || defined(options.max);
                if (userSetLimits) {
                    if (options.min === options.max) {
                        if (options.min > 0) {
                            options.min = 0;
                        } else {
                            options.max = 1;
                        }
                    }
                }

                if (options.majorUnit) {
                    autoOptions.min = floor(autoOptions.min, options.majorUnit);
                    autoOptions.max = ceil(autoOptions.max, options.majorUnit);
                } else if (userSetLimits) {
                    options = deepExtend(autoOptions, options);

                    // Determine an auto major unit after min/max have been set
                    autoOptions.majorUnit = autoMajorUnit(options.min, options.max);
                }
            }

            autoOptions.minorUnit = (options.majorUnit || autoOptions.majorUnit) / 5;

            return deepExtend(autoOptions, options);
        },

        range: function () {
            var options = this.options;
            return { min: options.min, max: options.max };
        },

        autoAxisMax: function (min, max, narrow) {
            var axisMax,
                diff;

            if (!min && !max) {
                return 1;
            }

            if (min <= 0 && max <= 0) {
                max = min == max ? 0 : max;

                diff = math.abs((max - min) / max);
                if (!narrow && diff > ZERO_THRESHOLD) {
                    return 0;
                }

                axisMax = math.min(0, max - ((min - max) / 2));
            } else {
                min = min == max ? 0 : min;
                axisMax = max;
            }

            return axisMax;
        },

        autoAxisMin: function (min, max, narrow) {
            var axisMin,
                diff;

            if (!min && !max) {
                return 0;
            }

            if (min >= 0 && max >= 0) {
                min = min == max ? 0 : min;

                diff = (max - min) / max;
                if (!narrow && diff > ZERO_THRESHOLD) {
                    return 0;
                }

                axisMin = math.max(0, min - ((max - min) / 2));
            } else {
                max = min == max ? 0 : max;
                axisMin = min;
            }

            return axisMin;
        },

        getDivisions: function (stepValue) {
            if (stepValue === 0) {
                return 1;
            }

            var options = this.options,
                range = options.max - options.min;

            return math.floor(round(range / stepValue, COORD_PRECISION)) + 1;
        },

        getTickPositions: function (unit, skipUnit) {
            var axis = this,
                options = axis.options,
                vertical = options.vertical,
                reverse = options.reverse,
                lineBox = axis.lineBox(),
                lineSize = vertical ? lineBox.height() : lineBox.width(),
                range = options.max - options.min,
                scale = lineSize / range,
                step = unit * scale,
                skipStep = 0,
                divisions = axis.getDivisions(unit),
                dir = (vertical ? -1 : 1) * (reverse ? -1 : 1),
                startEdge = dir === 1 ? 1 : 2,
                pos = lineBox[(vertical ? Y : X) + startEdge],
                positions = [],
                i;

            if (skipUnit) {
                skipStep = skipUnit / unit;
            }

            for (i = 0; i < divisions; i++) {
                if (i % skipStep !== 0) {
                    positions.push(round(pos, COORD_PRECISION));
                }

                pos = pos + step * dir;
            }

            return positions;
        },

        getMajorTickPositions: function () {
            var axis = this;

            return axis.getTickPositions(axis.options.majorUnit);
        },

        getMinorTickPositions: function () {
            var axis = this;

            return axis.getTickPositions(axis.options.minorUnit);
        },

        getSlot: function (a, b, limit) {
            var axis = this,
                options = axis.options,
                reverse = options.reverse,
                vertical = options.vertical,
                valueAxis = vertical ? Y : X,
                lineBox = axis.lineBox(),
                lineStart = lineBox[valueAxis + (reverse ? 2 : 1)],
                lineSize = vertical ? lineBox.height() : lineBox.width(),
                dir = reverse ? -1 : 1,
                step = dir * (lineSize / (options.max - options.min)),
                p1,
                p2,
                slotBox = new Box2D(lineBox.x1, lineBox.y1, lineBox.x1, lineBox.y1);

            if (!defined(a)) {
                a = b || 0;
            }

            if (!defined(b)) {
                b = a || 0;
            }

            if (limit) {
                a = math.max(math.min(a, options.max), options.min);
                b = math.max(math.min(b, options.max), options.min);
            }

            if (vertical) {
                p1 = options.max - math.max(a, b);
                p2 = options.max - math.min(a, b);
            } else {
                p1 = math.min(a, b) - options.min;
                p2 = math.max(a, b) - options.min;
            }

            slotBox[valueAxis + 1] = math.max(math.min(lineStart + step * (reverse ? p2 : p1), COORDINATE_LIMIT), -COORDINATE_LIMIT);
            slotBox[valueAxis + 2] = math.max(math.min(lineStart + step * (reverse ? p1 : p2), COORDINATE_LIMIT), -COORDINATE_LIMIT);

            return slotBox;
        },

        getValue: function (point) {
            var axis = this,
                options = axis.options,
                reverse = options.reverse,
                vertical = options.vertical,
                max = options.max * 1,
                min = options.min * 1,
                valueAxis = vertical ? Y : X,
                lineBox = axis.lineBox(),
                lineStart = lineBox[valueAxis + (reverse ? 2 : 1)],
                lineSize = vertical ? lineBox.height() : lineBox.width(),
                dir = reverse ? -1 : 1,
                offset = dir * (point[valueAxis] - lineStart),
                step = (max - min) / lineSize,
                valueOffset = offset * step,
                value;

            if (offset < 0 || offset > lineSize) {
                return null;
            }

            value = vertical ?
                    max - valueOffset :
                    min + valueOffset;

            return round(value, DEFAULT_PRECISION);
        },

        translateRange: function (delta) {
            var axis = this,
                options = axis.options,
                lineBox = axis.lineBox(),
                vertical = options.vertical,
                reverse = options.reverse,
                size = vertical ? lineBox.height() : lineBox.width(),
                range = options.max - options.min,
                scale = size / range,
                offset = round(delta / scale, DEFAULT_PRECISION);

            if ((vertical || reverse) && !(vertical && reverse)) {
                offset = -offset;
            }

            return {
                min: options.min + offset,
                max: options.max + offset
            };
        },

        scaleRange: function (delta) {
            var axis = this,
                options = axis.options,
                offset = -delta * options.majorUnit;

            return {
                min: options.min - offset,
                max: options.max + offset
            };
        },

        labelsCount: function () {
            return this.getDivisions(this.options.majorUnit);
        },

        createAxisLabel: function (index, labelOptions) {
            var axis = this,
                options = axis.options,
                value = round(options.min + (index * options.majorUnit), DEFAULT_PRECISION),
                text = axis.axisLabelText(value, null, labelOptions);

            return new AxisLabel(value, text, index, null, labelOptions);
        },

        shouldRenderNote: function (value) {
            var range = this.range();
            return range.min <= value && value <= range.max;
        }
    });

    var LogarithmicAxis = Axis.extend({
        init: function (seriesMin, seriesMax, options) {
            this.options = this._initOptions(seriesMin, seriesMax, options);
            Axis.fn.init.call(this, options);
        },

        startValue: function () {
            return this.options.min;
        },

        options: {
            type: "log",
            majorUnit: 10,
            minorUnit: 1,
            axisCrossingValue: 1,
            vertical: true,
            majorGridLines: {
                visible: true,
                width: 1,
                color: BLACK
            },
            zIndex: 1
        },

        getSlot: function (a, b, limit) {
            var axis = this,
                options = axis.options,
                reverse = options.reverse,
                vertical = options.vertical,
                valueAxis = vertical ? Y : X,
                lineBox = axis.lineBox(),
                lineStart = lineBox[valueAxis + (reverse ? 2 : 1)],
                lineSize = vertical ? lineBox.height() : lineBox.width(),
                dir = reverse ? -1 : 1,
                base = options.majorUnit,
                logMin = axis.logMin,
                logMax = axis.logMax,
                step = dir * (lineSize / (logMax - logMin)),
                p1, p2,
                slotBox = new Box2D(lineBox.x1, lineBox.y1, lineBox.x1, lineBox.y1);

            if (!defined(a)) {
                a = b || 1;
            }

            if (!defined(b)) {
                b = a || 1;
            }

            if (a <= 0 || b <= 0) {
                return;
            }

            if (limit) {
                a = math.max(math.min(a, options.max), options.min);
                b = math.max(math.min(b, options.max), options.min);
            }

            a = log(a, base);
            b = log(b, base);

            if (vertical) {
                p1 = logMax - math.max(a, b);
                p2 = logMax - math.min(a, b);
            } else {
                p1 = math.min(a, b) - logMin;
                p2 = math.max(a, b) - logMin;
            }

            slotBox[valueAxis + 1] = lineStart + step * (reverse ? p2 : p1);
            slotBox[valueAxis + 2] = lineStart + step * (reverse ? p1 : p2);

            return slotBox;
        },

        getValue: function (point) {
            var axis = this,
                options = axis.options,
                reverse = options.reverse,
                vertical = options.vertical,
                lineBox = axis.lineBox(),
                base = options.majorUnit,
                logMin = axis.logMin,
                logMax = axis.logMax,
                dir = vertical === reverse ? 1 : -1,
                startEdge = dir === 1 ? 1 : 2,
                lineSize = vertical ? lineBox.height() : lineBox.width(),
                step = ((logMax - logMin) / lineSize),
                valueAxis = vertical ? Y : X,
                lineStart = lineBox[valueAxis + startEdge],
                offset = dir * (point[valueAxis] - lineStart),
                valueOffset = offset * step,
                value;

            if (offset < 0 || offset > lineSize) {
                return null;
            }

            value = logMin + valueOffset;

            return round(math.pow(base, value), DEFAULT_PRECISION);
        },

        range: function () {
            var options = this.options;
            return { min: options.min, max: options.max };
        },

        scaleRange: function (delta) {
            var axis = this,
                options = axis.options,
                base = options.majorUnit,
                offset = -delta;

            return {
                min: math.pow(base, axis.logMin - offset),
                max: math.pow(base, axis.logMax + offset)
            };
        },

        translateRange: function (delta) {
            var axis = this,
                options = axis.options,
                base = options.majorUnit,
                lineBox = axis.lineBox(),
                vertical = options.vertical,
                reverse = options.reverse,
                size = vertical ? lineBox.height() : lineBox.width(),
                logMin = axis.logMin,
                logMax = axis.logMax,
                scale = size / (axis.logMax - axis.logMin),
                offset = round(delta / scale, DEFAULT_PRECISION);

            if ((vertical || reverse) && !(vertical && reverse)) {
                offset = -offset;
            }

            return {
                min: math.pow(base, axis.logMin + offset),
                max: math.pow(base, axis.logMax + offset)
            };
        },

        labelsCount: function () {
            var axis = this,
                floorMax = math.floor(axis.logMax),
                count = math.floor(floorMax - axis.logMin) + 1;

            return count;
        },

        getMajorTickPositions: function () {
            var axis = this,
                ticks = [];

            axis.traverseMajorTicksPositions(function (position) {
                ticks.push(position);
            }, { step: 1, skip: 0 });
            return ticks;
        },

        createTicks: function (lineGroup) {
            var axis = this,
                ticks = [],
                options = axis.options,
                lineBox = axis.lineBox(),
                mirror = options.labels.mirror,
                majorTicks = options.majorTicks,
                minorTicks = options.minorTicks,
                tickLineOptions = {
                    // TODO
                    // _alignLines: options._alignLines,
                    vertical: options.vertical
                },
                start, end;

            function render(tickPosition, tickOptions) {
                tickLineOptions.tickX = mirror ? lineBox.x2 : lineBox.x2 - tickOptions.size;
                tickLineOptions.tickY = mirror ? lineBox.y1 - tickOptions.size : lineBox.y1;
                tickLineOptions.position = tickPosition;

                lineGroup.append(createAxisTick(tickLineOptions, tickOptions));
            }

            if (majorTicks.visible) {
                axis.traverseMajorTicksPositions(render, majorTicks);
            }

            if (minorTicks.visible) {
                axis.traverseMinorTicksPositions(render, minorTicks);
            }

            return ticks;
        },

        createGridLines: function (altAxis) {
            var axis = this,
                options = axis.options,
                axisLineVisible = altAxis.options.line.visible,
                majorGridLines = options.majorGridLines,
                minorGridLines = options.minorGridLines,
                vertical = options.vertical,
                lineBox = altAxis.lineBox(),
                lineOptions = {
                    lineStart: lineBox[vertical ? "x1" : "y1"],
                    lineEnd: lineBox[vertical ? "x2" : "y2"],
                    vertical: vertical
                },
                pos, majorTicks = [];

            var container = this.gridLinesVisual();
            function render(tickPosition, gridLine) {
                if (!inArray(tickPosition, majorTicks)) {
                    lineOptions.position = tickPosition;
                    container.append(createAxisGridLine(lineOptions, gridLine));

                    majorTicks.push(tickPosition);
                }
            }

            if (majorGridLines.visible) {
                axis.traverseMajorTicksPositions(render, majorGridLines);
            }

            if (minorGridLines.visible) {
                axis.traverseMinorTicksPositions(render, minorGridLines);
            }

            return container.children;
        },

        traverseMajorTicksPositions: function (callback, tickOptions) {
            var axis = this,
                lineOptions = axis._lineOptions(),
                lineStart = lineOptions.lineStart,
                step = lineOptions.step,
                logMin = axis.logMin,
                logMax = axis.logMax,
                power,
                position;

            for (power = math.ceil(logMin) + tickOptions.skip; power <= logMax; power += tickOptions.step) {
                position = round(lineStart + step * (power - logMin), DEFAULT_PRECISION);
                callback(position, tickOptions);
            }
        },

        traverseMinorTicksPositions: function (callback, tickOptions) {
            var axis = this,
                options = axis.options,
                lineOptions = axis._lineOptions(),
                lineStart = lineOptions.lineStart,
                lineStep = lineOptions.step,
                base = options.majorUnit,
                logMin = axis.logMin,
                logMax = axis.logMax,
                start = math.floor(logMin),
                max = options.max,
                min = options.min,
                minorUnit = options.minorUnit,
                power,
                value,
                position,
                minorOptions;

            for (power = start; power < logMax; power++) {
                minorOptions = axis._minorIntervalOptions(power);
                for (var idx = tickOptions.skip; idx < minorUnit; idx += tickOptions.step) {
                    value = minorOptions.value + idx * minorOptions.minorStep;
                    if (value > max) {
                        break;
                    }
                    if (value >= min) {
                        position = round(lineStart + lineStep * (log(value, base) - logMin), DEFAULT_PRECISION);
                        callback(position, tickOptions);
                    }
                }
            }
        },

        createAxisLabel: function (index, labelOptions) {
            var axis = this,
                options = axis.options,
                power = math.ceil(axis.logMin + index),
                value = Math.pow(options.majorUnit, power),
                text = axis.axisLabelText(value, null, labelOptions);

            return new AxisLabel(value, text, index, null, labelOptions);
        },

        shouldRenderNote: function (value) {
            var range = this.range();
            return range.min <= value && value <= range.max;
        },

        _throwNegativeValuesError: function () {
            throw new Error("Non positive values cannot be used for a logarithmic axis");
        },

        _initOptions: function (seriesMin, seriesMax, options) {
            var axis = this,
                axisOptions = deepExtend({}, axis.options, { min: seriesMin, max: seriesMax }, options),
                min = axisOptions.min,
                max = axisOptions.max,
                base = axisOptions.majorUnit,
                logMaxRemainder;

            if (axisOptions.axisCrossingValue <= 0) {
                axis._throwNegativeValuesError();
            }

            if (!defined(options.max)) {
                logMaxRemainder = round(log(max, base), DEFAULT_PRECISION) % 1;
                if (max <= 0) {
                    max = base;
                } else if (logMaxRemainder !== 0 && (logMaxRemainder < 0.3 || logMaxRemainder > 0.9)) {
                    max = math.pow(base, log(max, base) + 0.2);
                } else {
                    max = math.pow(base, math.ceil(log(max, base)));
                }
            } else if (options.max <= 0) {
                axis._throwNegativeValuesError();
            }

            if (!defined(options.min)) {
                if (min <= 0) {
                    min = max <= 1 ? math.pow(base, -2) : 1;
                } else if (!options.narrowRange) {
                    min = math.pow(base, math.floor(log(min, base)));
                }
            } else if (options.min <= 0) {
                axis._throwNegativeValuesError();
            }

            axis.logMin = round(log(min, base), DEFAULT_PRECISION);
            axis.logMax = round(log(max, base), DEFAULT_PRECISION);
            axisOptions.max = max;
            axisOptions.min = min;
            axisOptions.minorUnit = options.minorUnit || round(base - 1, DEFAULT_PRECISION);

            return axisOptions;
        },

        _minorIntervalOptions: function (power) {
            var base = this.options.majorUnit,
                value = math.pow(base, power),
                nextValue = math.pow(base, power + 1),
                difference = nextValue - value,
                minorStep = difference / this.options.minorUnit;
            return {
                value: value,
                minorStep: minorStep
            };
        },

        _lineOptions: function () {
            var axis = this,
                options = axis.options,
                reverse = options.reverse,
                vertical = options.vertical,
                valueAxis = vertical ? Y : X,
                lineBox = axis.lineBox(),
                dir = vertical === reverse ? 1 : -1,
                startEdge = dir === 1 ? 1 : 2,
                lineSize = vertical ? lineBox.height() : lineBox.width(),
                step = dir * (lineSize / (axis.logMax - axis.logMin)),
                lineStart = lineBox[valueAxis + startEdge];

            return {
                step: step,
                lineStart: lineStart,
                lineBox: lineBox
            };
        }
    });

    dataviz.Gradients = {
        glass: {
            type: LINEAR,
            rotation: 0,
            stops: [{
                offset: 0,
                color: WHITE,
                opacity: 0
            }, {
                offset: 0.25,
                color: WHITE,
                opacity: 0.3
            }, {
                offset: 1,
                color: WHITE,
                opacity: 0
            }]
        },
        sharpBevel: {
            type: RADIAL,
            stops: [{
                offset: 0,
                color: WHITE,
                opacity: 0.55
            }, {
                offset: 0.65,
                color: WHITE,
                opacity: 0
            }, {
                offset: 0.95,
                color: WHITE,
                opacity: 0.25
            }]
        },
        roundedBevel: {
            type: RADIAL,
            stops: [{
                offset: 0.33,
                color: WHITE,
                opacity: 0.06
            }, {
                offset: 0.83,
                color: WHITE,
                opacity: 0.2
            }, {
                offset: 0.95,
                color: WHITE,
                opacity: 0
            }]
        },
        roundedGlass: {
            type: RADIAL,
            supportVML: false,
            stops: [{
                offset: 0,
                color: WHITE,
                opacity: 0
            }, {
                offset: 0.5,
                color: WHITE,
                opacity: 0.3
            }, {
                offset: 0.99,
                color: WHITE,
                opacity: 0
            }]
        },
        sharpGlass: {
            type: RADIAL,
            supportVML: false,
            stops: [{
                offset: 0,
                color: WHITE,
                opacity: 0.2
            }, {
                offset: 0.15,
                color: WHITE,
                opacity: 0.15
            }, {
                offset: 0.17,
                color: WHITE,
                opacity: 0.35
            }, {
                offset: 0.85,
                color: WHITE,
                opacity: 0.05
            }, {
                offset: 0.87,
                color: WHITE,
                opacity: 0.15
            }, {
                offset: 0.99,
                color: WHITE,
                opacity: 0
            }]
        }
    };

    // Helper functions========================================================
    var ExportMixin = {
        extend: function (proto, skipLegacy) {
            if (!proto.exportVisual) {
                throw new Error("Mixin target has no exportVisual method defined.");
            }

            proto.exportSVG = this.exportSVG;
            proto.exportImage = this.exportImage;
            proto.exportPDF = this.exportPDF;

            if (!skipLegacy) {
                proto.svg = this.svg;
                proto.imageDataURL = this.imageDataURL;
            }
        },

        exportSVG: function (options) {
            return draw.exportSVG(this.exportVisual(), options);
        },

        exportImage: function (options) {
            return draw.exportImage(this.exportVisual(), options);
        },

        exportPDF: function (options) {
            return draw.exportPDF(this.exportVisual(), options);
        },

        svg: function () {
            if (draw.svg.Surface) {
                return draw.svg._exportGroup(this.exportVisual());
            } else {
                throw new Error("SVG Export failed. Unable to export instantiate kendo.drawing.svg.Surface");
            }
        },

        imageDataURL: function () {
            if (!kendo.support.canvas) {
                return null;
            }

            if (draw.canvas.Surface) {
                var container = $("<div />").css({
                    display: "none",
                    width: this.element.width(),
                    height: this.element.height()
                }).appendTo(document.body);

                var surface = new draw.canvas.Surface(container);
                surface.draw(this.exportVisual());
                var image = surface._rootElement.toDataURL();

                surface.destroy();
                container.remove();

                return image;
            } else {
                throw new Error("Image Export failed. Unable to export instantiate kendo.drawing.canvas.Surface");
            }
        }
    };

    function autoMajorUnit(min, max) {
        var diff = round(max - min, DEFAULT_PRECISION - 1);

        if (diff === 0) {
            if (max === 0) {
                return 0.1;
            }

            diff = math.abs(max);
        }

        var scale = math.pow(10, math.floor(math.log(diff) / math.log(10))),
            relativeValue = round((diff / scale), DEFAULT_PRECISION),
            scaleMultiplier = 1;

        if (relativeValue < 1.904762) {
            scaleMultiplier = 0.2;
        } else if (relativeValue < 4.761904) {
            scaleMultiplier = 0.5;
        } else if (relativeValue < 9.523809) {
            scaleMultiplier = 1;
        } else {
            scaleMultiplier = 2;
        }

        return round(scale * scaleMultiplier, DEFAULT_PRECISION);
    }

    function getHash(object) {
        var hash = [];
        for (var key in object) {
            hash.push(key + object[key]);
        }

        return hash.sort().join(" ");
    }

    // TODO: Replace with Point2D.rotate
    function rotatePoint(x, y, cx, cy, angle) {
        var theta = angle * DEG_TO_RAD;

        return new Point2D(
            cx + (x - cx) * math.cos(theta) + (y - cy) * math.sin(theta),
            cy - (x - cx) * math.sin(theta) + (y - cy) * math.cos(theta)
        );
    }

    function boxDiff(r, s) {
        if (r.x1 == s.x1 && r.y1 == s.y1 && r.x2 == s.x2 && r.y2 == s.y2) {
            return s;
        }

        var a = math.min(r.x1, s.x1),
            b = math.max(r.x1, s.x1),
            c = math.min(r.x2, s.x2),
            d = math.max(r.x2, s.x2),
            e = math.min(r.y1, s.y1),
            f = math.max(r.y1, s.y1),
            g = math.min(r.y2, s.y2),
            h = math.max(r.y2, s.y2),
            result = [];

        // X = intersection, 0-7 = possible difference areas
        // h +-+-+-+
        // . |5|6|7|
        // g +-+-+-+
        // . |3|X|4|
        // f +-+-+-+
        // . |0|1|2|
        // e +-+-+-+
        // . a b c d

        // we'll always have rectangles 1, 3, 4 and 6
        result[0] = Box2D(b, e, c, f);
        result[1] = Box2D(a, f, b, g);
        result[2] = Box2D(c, f, d, g);
        result[3] = Box2D(b, g, c, h);

        // decide which corners
        if (r.x1 == a && r.y1 == e || s.x1 == a && s.y1 == e) { // corners 0 and 7
            result[4] = Box2D(a, e, b, f);
            result[5] = Box2D(c, g, d, h);
        }
        else { // corners 2 and 5
            result[4] = Box2D(c, e, d, f);
            result[5] = Box2D(a, g, b, h);
        }

        return $.grep(result, function (box) {
            return box.height() > 0 && box.width() > 0;
        })[0];
    }

    function inArray(value, array) {
        return indexOf(value, array) != -1;
    }

    function ceil(value, step) {
        return round(math.ceil(value / step) * step, DEFAULT_PRECISION);
    }

    function floor(value, step) {
        return round(math.floor(value / step) * step, DEFAULT_PRECISION);
    }

    function round(value, precision) {
        var power = math.pow(10, precision || 0);
        return math.round(value * power) / power;
    }

    function log(y, x) {
        return math.log(y) / math.log(x);
    }

    function remainderClose(value, divisor, ratio) {
        var remainder = round(math.abs(value % divisor), DEFAULT_PRECISION),
            threshold = divisor * (1 - ratio);

        return remainder === 0 || remainder > threshold;
    }

    function interpolateValue(start, end, progress) {
        return round(start + (end - start) * progress, COORD_PRECISION);
    }

    function numericComparer(a, b) {
        return a - b;
    }

    function updateArray(arr, prop, value) {
        var i,
            length = arr.length;

        for (i = 0; i < length; i++) {
            arr[i][prop] = value;
        }
    }

    function autoFormat(format, value) {
        if (format.match(FORMAT_REGEX)) {
            return kendo.format.apply(this, arguments);
        }

        return kendo.toString(value, format);
    }

    function detached(element) {
        var parent = element.parentNode;

        while (parent && parent.parentNode) {
            parent = parent.parentNode;
        }

        return parent !== doc;
    }

    function clockwise(v1, v2) {
        // True if v2 is clockwise of v1
        // assuming angles grow in clock-wise direction
        // (as in the pie and radar charts)
        return -v1.x * v2.y + v1.y * v2.x < 0;
    }

    function dateComparer(a, b) {
        if (a && b) {
            return a.getTime() - b.getTime();
        }

        return 0;
    }

    var CurveProcessor = function (closed) {
        this.closed = closed;
    };

    CurveProcessor.prototype = CurveProcessor.fn = {
        WEIGHT: 0.333,
        EXTREMUM_ALLOWED_DEVIATION: 0.01,

        process: function (dataPoints) {
            var that = this,
                closed = that.closed,
                points = dataPoints.slice(0),
                length = points.length,
                segments = [],
                p0, p1, p2,
                controlPoints,
                initialControlPoint,
                lastControlPoint,
                tangent;

            if (length > 2) {
                that.removeDuplicates(0, points);
                length = points.length;
            }

            if (length < 2 || (length == 2 && points[0].equals(points[1]))) {
                return segments;
            }

            p0 = points[0]; p1 = points[1]; p2 = points[2];
            segments.push(new draw.Segment(p0));

            while (p0.equals(points[length - 1])) {
                closed = true;
                points.pop();
                length--;
            }

            if (length == 2) {
                tangent = that.tangent(p0, p1, X, Y);

                last(segments).controlOut(
                    that.firstControlPoint(tangent, p0, p1, X, Y)
                );

                segments.push(new draw.Segment(
                    p1,
                    that.secondControlPoint(tangent, p0, p1, X, Y)
                ));

                return segments;
            }

            if (closed) {
                p0 = points[length - 1]; p1 = points[0]; p2 = points[1];
                controlPoints = that.controlPoints(p0, p1, p2);
                initialControlPoint = controlPoints[1];
                lastControlPoint = controlPoints[0];
            } else {
                tangent = that.tangent(p0, p1, X, Y);
                initialControlPoint = that.firstControlPoint(tangent, p0, p1, X, Y);
            }

            var cp0 = initialControlPoint;
            for (var idx = 0; idx <= length - 3; idx++) {
                that.removeDuplicates(idx, points);
                length = points.length;
                if (idx + 3 <= length) {
                    p0 = points[idx]; p1 = points[idx + 1]; p2 = points[idx + 2];
                    controlPoints = that.controlPoints(p0, p1, p2);

                    last(segments).controlOut(cp0);
                    cp0 = controlPoints[1];

                    var cp1 = controlPoints[0];
                    segments.push(new draw.Segment(p1, cp1));
                }
            }

            if (closed) {
                p0 = points[length - 2]; p1 = points[length - 1]; p2 = points[0];
                controlPoints = that.controlPoints(p0, p1, p2);

                last(segments).controlOut(cp0);
                segments.push(new draw.Segment(
                    p1,
                    controlPoints[0]
                ));

                last(segments).controlOut(controlPoints[1]);
                segments.push(new draw.Segment(
                    p2,
                    lastControlPoint
                ));
            } else {
                tangent = that.tangent(p1, p2, X, Y);

                last(segments).controlOut(cp0);
                segments.push(new draw.Segment(
                    p2,
                    that.secondControlPoint(tangent, p1, p2, X, Y)
                ));
            }

            return segments;
        },

        removeDuplicates: function (idx, points) {
            while (points[idx].equals(points[idx + 1]) || points[idx + 1].equals(points[idx + 2])) {
                points.splice(idx + 1, 1);
            }
        },

        invertAxis: function (p0, p1, p2) {
            var that = this,
                fn, y2,
                invertAxis = false;

            if (p0.x === p1.x) {
                invertAxis = true;
            } else if (p1.x === p2.x) {
                if ((p1.y < p2.y && p0.y <= p1.y) || (p2.y < p1.y && p1.y <= p0.y)) {
                    invertAxis = true;
                }
            } else {
                fn = that.lineFunction(p0, p1);
                y2 = that.calculateFunction(fn, p2.x);
                if (!(p0.y <= p1.y && p2.y <= y2) &&
                    !(p1.y <= p0.y && p2.y >= y2)) {
                    invertAxis = true;
                }
            }

            return invertAxis;
        },

        isLine: function (p0, p1, p2) {
            var that = this,
                fn = that.lineFunction(p0, p1),
                y2 = that.calculateFunction(fn, p2.x);

            return (p0.x == p1.x && p1.x == p2.x) || round(y2, 1) === round(p2.y, 1);
        },

        lineFunction: function (p1, p2) {
            var a = (p2.y - p1.y) / (p2.x - p1.x),
                b = p1.y - a * p1.x;
            return [b, a];
        },

        controlPoints: function (p0, p1, p2) {
            var that = this,
                xField = X,
                yField = Y,
                restrict = false,
                switchOrientation = false,
                tangent,
                monotonic,
                firstControlPoint,
                secondControlPoint,
                allowedDeviation = that.EXTREMUM_ALLOWED_DEVIATION;

            if (that.isLine(p0, p1, p2)) {
                tangent = that.tangent(p0, p1, X, Y);
            } else {
                monotonic = {
                    x: that.isMonotonicByField(p0, p1, p2, X),
                    y: that.isMonotonicByField(p0, p1, p2, Y)
                };

                if (monotonic.x && monotonic.y) {
                    tangent = that.tangent(p0, p2, X, Y);
                    restrict = true;
                } else {
                    if (that.invertAxis(p0, p1, p2)) {
                        xField = Y;
                        yField = X;
                    }

                    if (monotonic[xField]) {
                        tangent = 0;
                    } else {
                        var sign;
                        if ((p2[yField] < p0[yField] && p0[yField] <= p1[yField]) ||
                            (p0[yField] < p2[yField] && p1[yField] <= p0[yField])) {
                            sign = that.sign((p2[yField] - p0[yField]) * (p1[xField] - p0[xField]));
                        } else {
                            sign = -that.sign((p2[xField] - p0[xField]) * (p1[yField] - p0[yField]));
                        }

                        tangent = allowedDeviation * sign;
                        switchOrientation = true;
                    }
                }
            }

            secondControlPoint = that.secondControlPoint(tangent, p0, p1, xField, yField);

            if (switchOrientation) {
                var oldXField = xField;
                xField = yField;
                yField = oldXField;
            }

            firstControlPoint = that.firstControlPoint(tangent, p1, p2, xField, yField);

            if (restrict) {
                that.restrictControlPoint(p0, p1, secondControlPoint, tangent);
                that.restrictControlPoint(p1, p2, firstControlPoint, tangent);
            }

            return [secondControlPoint, firstControlPoint];
        },

        sign: function (x) {
            return x <= 0 ? -1 : 1;
        },

        restrictControlPoint: function (p1, p2, cp, tangent) {
            if (p1.y < p2.y) {
                if (p2.y < cp.y) {
                    cp.x = p1.x + (p2.y - p1.y) / tangent;
                    cp.y = p2.y;
                } else if (cp.y < p1.y) {
                    cp.x = p2.x - (p2.y - p1.y) / tangent;
                    cp.y = p1.y;
                }
            } else {
                if (cp.y < p2.y) {
                    cp.x = p1.x - (p1.y - p2.y) / tangent;
                    cp.y = p2.y;
                } else if (p1.y < cp.y) {
                    cp.x = p2.x + (p1.y - p2.y) / tangent;
                    cp.y = p1.y;
                }
            }
        },

        tangent: function (p0, p1, xField, yField) {
            var tangent,
                x = p1[xField] - p0[xField],
                y = p1[yField] - p0[yField];
            if (x === 0) {
                tangent = 0;
            } else {
                tangent = y / x;
            }

            return tangent;
        },

        isMonotonicByField: function (p0, p1, p2, field) {
            return (p2[field] > p1[field] && p1[field] > p0[field]) ||
                        (p2[field] < p1[field] && p1[field] < p0[field]);
        },

        firstControlPoint: function (tangent, p0, p3, xField, yField) {
            var that = this,
                t1 = p0[xField],
                t2 = p3[xField],
                distance = (t2 - t1) * that.WEIGHT;

            return that.point(t1 + distance, p0[yField] + distance * tangent, xField, yField);
        },

        secondControlPoint: function (tangent, p0, p3, xField, yField) {
            var that = this,
                t1 = p0[xField],
                t2 = p3[xField],
                distance = (t2 - t1) * that.WEIGHT;

            return that.point(t2 - distance, p3[yField] - distance * tangent, xField, yField);
        },

        point: function (xValue, yValue, xField, yField) {
            var controlPoint = new geom.Point();
            controlPoint[xField] = xValue;
            controlPoint[yField] = yValue;

            return controlPoint;
        },

        calculateFunction: function (fn, x) {
            var result = 0,
                length = fn.length;
            for (var i = 0; i < length; i++) {
                result += Math.pow(x, i) * fn[i];
            }
            return result;
        }
    };

    function mwDelta(e) {
        var origEvent = e.originalEvent,
            delta = 0;

        if (origEvent.wheelDelta) {
            delta = -origEvent.wheelDelta / 120;
            delta = delta > 0 ? math.ceil(delta) : math.floor(delta);
        }

        if (origEvent.detail) {
            delta = round(origEvent.detail / 3);
        }

        return delta;
    }

    function decodeEntities(text) {
        if (!text || !text.indexOf || text.indexOf("&") < 0) {
            return text;
        } else {
            var element = decodeEntities._element;
            element.innerHTML = text;
            return element.textContent || element.innerText;
        }
    }

    function isInRange(value, range) {
        return value >= range.min && value <= range.max;
    }

    function alignPathToPixel(path) {
        if (!kendo.support.vml) {
            for (var i = 0; i < path.segments.length; i++) {
                path.segments[i].anchor().round(0).translate(0.5, 0.5);
            }
        }

        return path;
    }

    function innerRadialStops(options) {
        var gradient = this,
            stops = options.stops,
            usedSpace = ((options.innerRadius / options.radius) * 100),
            i,
            length = stops.length,
            currentStop,
            currentStops = [];

        for (i = 0; i < length; i++) {
            currentStop = deepExtend({}, stops[i]);
            currentStop.offset = (currentStop.offset * (100 - usedSpace) + usedSpace) / 100;
            currentStops.push(currentStop);
        }

        return currentStops;
    }

    decodeEntities._element = document.createElement("span");

    // Exports ================================================================
    deepExtend(kendo.dataviz, {
        AXIS_LABEL_CLICK: AXIS_LABEL_CLICK,
        COORD_PRECISION: COORD_PRECISION,
        DEFAULT_PRECISION: DEFAULT_PRECISION,
        DEFAULT_WIDTH: DEFAULT_WIDTH,
        DEFAULT_HEIGHT: DEFAULT_HEIGHT,
        DEFAULT_FONT: DEFAULT_FONT,
        INITIAL_ANIMATION_DURATION: INITIAL_ANIMATION_DURATION,
        NOTE_CLICK: NOTE_CLICK,
        NOTE_HOVER: NOTE_HOVER,
        CLIP: CLIP,

        Axis: Axis,
        AxisLabel: AxisLabel,
        Box2D: Box2D,
        BoxElement: BoxElement,
        ChartElement: ChartElement,
        CurveProcessor: CurveProcessor,
        ExportMixin: ExportMixin,
        FloatElement: FloatElement,
        LogarithmicAxis: LogarithmicAxis,
        Note: Note,
        NumericAxis: NumericAxis,
        Point2D: Point2D,
        Ring: Ring,
        RootElement: RootElement,
        Sector: Sector,
        ShapeBuilder: ShapeBuilder,
        ShapeElement: ShapeElement,
        Text: Text,
        TextBox: TextBox,
        Title: Title,

        alignPathToPixel: alignPathToPixel,
        autoFormat: autoFormat,
        autoMajorUnit: autoMajorUnit,
        boxDiff: boxDiff,
        dateComparer: dateComparer,
        decodeEntities: decodeEntities,
        getSpacing: getSpacing,
        inArray: inArray,
        interpolateValue: interpolateValue,
        mwDelta: mwDelta,
        rotatePoint: rotatePoint,
        round: round,
        ceil: ceil,
        floor: floor
    });

})(window.kendo.jQuery);





(function () {

    // Imports ================================================================
    var kendo = window.kendo,
        ui = kendo.dataviz.ui,
        deepExtend = kendo.deepExtend;

    // Constants ==============================================================
    var BAR_GAP = 1.5,
        BAR_SPACING = 0.4,
        BLACK = "#000",
        SANS = "Arial,Helvetica,sans-serif",
        SANS11 = "11px " + SANS,
        SANS12 = "12px " + SANS,
        SANS16 = "16px " + SANS,
        WHITE = "#fff";

    var chartBaseTheme = {
        title: {
            font: SANS16
        },
        legend: {
            labels: {
                font: SANS12
            }
        },
        seriesDefaults: {
            visible: true,
            labels: {
                font: SANS11
            },
            donut: {
                margin: 1
            },
            line: {
                width: 2
            },
            vericalLine: {
                width: 2
            },
            scatterLine: {
                width: 1
            },
            area: {
                opacity: 0.4,
                markers: {
                    visible: false,
                    size: 6
                },
                highlight: {
                    markers: {
                        border: {
                            color: "#fff",
                            opacity: 1,
                            width: 1
                        }
                    }
                },
                line: {
                    opacity: 1,
                    width: 0
                }
            },
            verticalArea: {
                opacity: 0.4,
                markers: {
                    visible: false,
                    size: 6
                },
                line: {
                    opacity: 1,
                    width: 0
                }
            },
            radarLine: {
                width: 2,
                markers: {
                    visible: false
                }
            },
            radarArea: {
                opacity: 0.5,
                markers: {
                    visible: false,
                    size: 6
                },
                line: {
                    opacity: 1,
                    width: 0
                }
            },
            candlestick: {
                line: {
                    width: 1,
                    color: BLACK
                },
                border: {
                    width: 1,
                    _brightness: 0.8
                },
                gap: 1,
                spacing: 0.3,
                downColor: WHITE,
                highlight: {
                    line: {
                        width: 2
                    },
                    border: {
                        width: 2,
                        opacity: 1
                    }
                }
            },
            ohlc: {
                line: {
                    width: 1
                },
                gap: 1,
                spacing: 0.3,
                highlight: {
                    line: {
                        width: 3,
                        opacity: 1
                    }
                }
            },
            bubble: {
                opacity: 0.6,
                border: {
                    width: 0
                },
                labels: {
                    background: "transparent"
                }
            },
            bar: {
                gap: BAR_GAP,
                spacing: BAR_SPACING
            },
            column: {
                gap: BAR_GAP,
                spacing: BAR_SPACING
            },
            rangeColumn: {
                gap: BAR_GAP,
                spacing: BAR_SPACING
            },
            rangeBar: {
                gap: BAR_GAP,
                spacing: BAR_SPACING
            },
            waterfall: {
                gap: 0.5,
                spacing: BAR_SPACING,
                line: {
                    width: 1,
                    color: BLACK
                }
            },
            horizontalWaterfall: {
                gap: 0.5,
                spacing: BAR_SPACING,
                line: {
                    width: 1,
                    color: BLACK
                }
            },
            bullet: {
                gap: BAR_GAP,
                spacing: BAR_SPACING,
                target: {
                    color: "#ff0000"
                }
            },
            verticalBullet: {
                gap: BAR_GAP,
                spacing: BAR_SPACING,
                target: {
                    color: "#ff0000"
                }
            },
            boxPlot: {
                outliersField: "",
                meanField: "",
                whiskers: {
                    width: 1,
                    color: BLACK
                },
                mean: {
                    width: 1,
                    color: BLACK
                },
                median: {
                    width: 1,
                    color: BLACK
                },
                border: {
                    width: 1,
                    _brightness: 0.8
                },
                gap: 1,
                spacing: 0.3,
                downColor: WHITE,
                highlight: {
                    whiskers: {
                        width: 2
                    },
                    border: {
                        width: 2,
                        opacity: 1
                    }
                }
            },
            funnel: {
                labels: {
                    color: "",
                    background: ""
                }
            },
            notes: {
                icon: {
                    size: 7,
                    border: {
                        width: 1
                    }
                },
                label: {
                    padding: 3,
                    font: SANS12
                },
                line: {
                    length: 10,
                    width: 1
                },
                visible: true
            }
        },
        categoryAxis: {
            majorGridLines: {
                visible: true
            }
        },
        axisDefaults: {
            labels: {
                font: SANS12
            },
            title: {
                font: SANS16,
                margin: 5
            },
            crosshair: {
                tooltip: {
                    font: SANS12
                }
            },
            notes: {
                icon: {
                    size: 7,
                    border: {
                        width: 1
                    }
                },
                label: {
                    padding: 3,
                    font: SANS12
                },
                line: {
                    length: 10,
                    width: 1
                },
                visible: true
            }
        },
        tooltip: {
            font: SANS12
        },
        navigator: {
            pane: {
                height: 90,
                margin: {
                    top: 10
                }
            }
        }
    };

    var gaugeBaseTheme = {
        scale: {
            labels: {
                font: SANS12
            }
        }
    };

    var diagramBaseTheme = {
        shapeDefaults: {
            hover: {
                opacity: 0.2
            },
            stroke: {
                width: 0
            }
        },
        editable: {
            resize: {
                handles: {
                    width: 7,
                    height: 7
                }
            }
        },
        selectable: {
            stroke: {
                width: 1,
                dashType: "dot"
            }
        },
        connectionDefaults: {
            stroke: {
                width: 2
            },
            selection: {
                handles: {
                    width: 8,
                    height: 8
                }
            },
            editable: {
                tools: ["edit", "delete"]
            }
        }
    };

    var themes = ui.themes,
        registerTheme = ui.registerTheme = function (themeName, options) {
            var result = {};
            // Apply base theme
            result.chart = deepExtend({}, chartBaseTheme, options.chart);
            result.gauge = deepExtend({}, gaugeBaseTheme, options.gauge);
            result.diagram = deepExtend({}, diagramBaseTheme, options.diagram);
            result.treeMap = deepExtend({}, options.treeMap);

            // Copy the line/area chart settings for their vertical counterparts
            var defaults = result.chart.seriesDefaults;
            defaults.verticalLine = deepExtend({}, defaults.line);
            defaults.verticalArea = deepExtend({}, defaults.area);

            defaults.polarArea = deepExtend({}, defaults.radarArea);
            defaults.polarLine = deepExtend({}, defaults.radarLine);

            themes[themeName] = result;
        };

    registerTheme("black", {
        chart: {
            title: {
                color: WHITE
            },
            legend: {
                labels: {
                    color: WHITE
                },
                inactiveItems: {
                    labels: {
                        color: "#919191"
                    },
                    markers: {
                        color: "#919191"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: WHITE
                },
                errorBars: {
                    color: WHITE
                },
                notes: {
                    icon: {
                        background: "#3b3b3b",
                        border: {
                            color: "#8e8e8e"
                        }
                    },
                    label: {
                        color: WHITE
                    },
                    line: {
                        color: "#8e8e8e"
                    }
                },
                pie: {
                    overlay: {
                        gradient: "sharpBevel"
                    }
                },
                donut: {
                    overlay: {
                        gradient: "sharpGlass"
                    }
                },
                line: {
                    markers: {
                        background: "#3d3d3d"
                    }
                },
                scatter: {
                    markers: {
                        background: "#3d3d3d"
                    }
                },
                scatterLine: {
                    markers: {
                        background: "#3d3d3d"
                    }
                },
                waterfall: {
                    line: {
                        color: "#8e8e8e"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#8e8e8e"
                    }
                },
                candlestick: {
                    downColor: "#555",
                    line: {
                        color: WHITE
                    },
                    border: {
                        _brightness: 1.5,
                        opacity: 1
                    },
                    highlight: {
                        border: {
                            color: WHITE,
                            opacity: 0.2
                        }
                    }
                },
                ohlc: {
                    line: {
                        color: WHITE
                    }
                }
            },
            chartArea: {
                background: "#3d3d3d"
            },
            seriesColors: ["#0081da", "#3aafff", "#99c900", "#ffeb3d", "#b20753", "#ff4195"],
            axisDefaults: {
                line: {
                    color: "#8e8e8e"
                },
                labels: {
                    color: WHITE
                },
                majorGridLines: {
                    color: "#545454"
                },
                minorGridLines: {
                    color: "#454545"
                },
                title: {
                    color: WHITE
                },
                crosshair: {
                    color: "#8e8e8e"
                },
                notes: {
                    icon: {
                        background: "#3b3b3b",
                        border: {
                            color: "#8e8e8e"
                        }
                    },
                    label: {
                        color: WHITE
                    },
                    line: {
                        color: "#8e8e8e"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#0070e4"
            },
            scale: {
                rangePlaceholderColor: "#1d1d1d",
                labels: {
                    color: WHITE
                },
                minorTicks: {
                    color: WHITE
                },
                majorTicks: {
                    color: WHITE
                },
                line: {
                    color: WHITE
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#0066cc"
                },
                connectorDefaults: {
                    fill: {
                        color: WHITE
                    },
                    stroke: {
                        color: "#384049"
                    },
                    hover: {
                        fill: {
                            color: "#3d3d3d"
                        },
                        stroke: {
                            color: "#efefef"
                        }
                    }
                },
                content: {
                    color: WHITE
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: "#3d3d3d"
                        },
                        stroke: {
                            color: WHITE
                        },
                        hover: {
                            fill: {
                                color: WHITE
                            },
                            stroke: {
                                color: WHITE
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: WHITE
                        },
                        fill: {
                            color: WHITE
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: WHITE
                }
            },
            connectionDefaults: {
                stroke: {
                    color: WHITE
                },
                content: {
                    color: WHITE
                },
                selection: {
                    handles: {
                        fill: {
                            color: "#3d3d3d"
                        },
                        stroke: {
                            color: "#efefef"
                        }
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#0081da", "#314b5c"],
                ["#3aafff", "#3c5464"],
                ["#99c900", "#4f5931"],
                ["#ffeb3d", "#64603d"],
                ["#b20753", "#543241"],
                ["#ff4195", "#643e4f"]]
        }
    });

    registerTheme("blueopal", {
        chart: {
            title: {
                color: "#293135"
            },
            legend: {
                labels: {
                    color: "#293135"
                },
                inactiveItems: {
                    labels: {
                        color: "#27A5BA"
                    },
                    markers: {
                        color: "#27A5BA"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: BLACK,
                    background: WHITE,
                    opacity: 0.5
                },
                errorBars: {
                    color: "#293135"
                },
                candlestick: {
                    downColor: "#c4d0d5",
                    line: {
                        color: "#9aabb2"
                    }
                },
                waterfall: {
                    line: {
                        color: "#9aabb2"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#9aabb2"
                    }
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#9aabb2"
                        }
                    },
                    label: {
                        color: "#293135"
                    },
                    line: {
                        color: "#9aabb2"
                    }
                }
            },
            seriesColors: ["#0069a5", "#0098ee", "#7bd2f6", "#ffb800", "#ff8517", "#e34a00"],
            axisDefaults: {
                line: {
                    color: "#9aabb2"
                },
                labels: {
                    color: "#293135"
                },
                majorGridLines: {
                    color: "#c4d0d5"
                },
                minorGridLines: {
                    color: "#edf1f2"
                },
                title: {
                    color: "#293135"
                },
                crosshair: {
                    color: "#9aabb2"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#9aabb2"
                        }
                    },
                    label: {
                        color: "#293135"
                    },
                    line: {
                        color: "#9aabb2"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#005c83"
            },
            scale: {
                rangePlaceholderColor: "#daecf4",

                labels: {
                    color: "#293135"
                },
                minorTicks: {
                    color: "#293135"
                },
                majorTicks: {
                    color: "#293135"
                },
                line: {
                    color: "#293135"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#7ec6e3"
                },
                connectorDefaults: {
                    fill: {
                        color: "#003f59"
                    },
                    stroke: {
                        color: WHITE
                    },
                    hover: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#003f59"
                        }
                    }
                },
                content: {
                    color: "#293135"
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#003f59"
                        },
                        hover: {
                            fill: {
                                color: "#003f59"
                            },
                            stroke: {
                                color: "#003f59"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: "#003f59"
                        },
                        fill: {
                            color: "#003f59"
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#003f59"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: "#003f59"
                },
                content: {
                    color: "#293135"
                },
                selection: {
                    handles: {
                        fill: {
                            color: "#3d3d3d"
                        },
                        stroke: {
                            color: "#efefef"
                        }
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#0069a5", "#bad7e7"],
                ["#0098ee", "#b9e0f5"],
                ["#7bd2f6", "#ceeaf6"],
                ["#ffb800", "#e6e3c4"],
                ["#ff8517", "#e4d8c8"],
                ["#e34a00", "#ddccc2"]
            ]
        }
    });

    registerTheme("highcontrast", {
        chart: {
            title: {
                color: "#ffffff"
            },
            legend: {
                labels: {
                    color: "#ffffff"
                },
                inactiveItems: {
                    labels: {
                        color: "#66465B"
                    },
                    markers: {
                        color: "#66465B"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: "#ffffff"
                },
                errorBars: {
                    color: "#ffffff"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#ffffff"
                        }
                    },
                    label: {
                        color: "#ffffff"
                    },
                    line: {
                        color: "#ffffff"
                    }
                },
                pie: {
                    overlay: {
                        gradient: "sharpGlass"
                    }
                },
                donut: {
                    overlay: {
                        gradient: "sharpGlass"
                    }
                },
                line: {
                    markers: {
                        background: "#2c232b"
                    }
                },
                scatter: {
                    markers: {
                        background: "#2c232b"
                    }
                },
                scatterLine: {
                    markers: {
                        background: "#2c232b"
                    }
                },
                area: {
                    opacity: 0.5
                },
                waterfall: {
                    line: {
                        color: "#ffffff"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#ffffff"
                    }
                },
                candlestick: {
                    downColor: "#664e62",
                    line: {
                        color: "#ffffff"
                    },
                    border: {
                        _brightness: 1.5,
                        opacity: 1
                    },
                    highlight: {
                        border: {
                            color: "#ffffff",
                            opacity: 1
                        }
                    }
                },
                ohlc: {
                    line: {
                        color: "#ffffff"
                    }
                }
            },
            chartArea: {
                background: "#2c232b"
            },
            seriesColors: ["#a7008f", "#ffb800", "#3aafff", "#99c900", "#b20753", "#ff4195"],
            axisDefaults: {
                line: {
                    color: "#ffffff"
                },
                labels: {
                    color: "#ffffff"
                },
                majorGridLines: {
                    color: "#664e62"
                },
                minorGridLines: {
                    color: "#4f394b"
                },
                title: {
                    color: "#ffffff"
                },
                crosshair: {
                    color: "#ffffff"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#ffffff"
                        }
                    },
                    label: {
                        color: "#ffffff"
                    },
                    line: {
                        color: "#ffffff"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#a7008f"
            },
            scale: {
                rangePlaceholderColor: "#2c232b",

                labels: {
                    color: "#ffffff"
                },
                minorTicks: {
                    color: "#2c232b"
                },
                majorTicks: {
                    color: "#664e62"
                },
                line: {
                    color: "#ffffff"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#a7018f"
                },
                connectorDefaults: {
                    fill: {
                        color: WHITE
                    },
                    stroke: {
                        color: "#2c232b"
                    },
                    hover: {
                        fill: {
                            color: "#2c232b"
                        },
                        stroke: {
                            color: WHITE
                        }
                    }
                },
                content: {
                    color: WHITE
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: "#2c232b"
                        },
                        stroke: {
                            color: WHITE
                        },
                        hover: {
                            fill: {
                                color: WHITE
                            },
                            stroke: {
                                color: WHITE
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: WHITE
                        },
                        fill: {
                            color: WHITE
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: WHITE
                }
            },
            connectionDefaults: {
                stroke: {
                    color: WHITE
                },
                content: {
                    color: WHITE
                },
                selection: {
                    handles: {
                        fill: {
                            color: "#2c232b"
                        },
                        stroke: {
                            color: WHITE
                        }
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#a7008f", "#451c3f"],
                ["#ffb800", "#564122"],
                ["#3aafff", "#2f3f55"],
                ["#99c900", "#424422"],
                ["#b20753", "#471d33"],
                ["#ff4195", "#562940"]
            ]
        }
    });

    registerTheme("default", {
        chart: {
            title: {
                color: "#8e8e8e"
            },
            legend: {
                labels: {
                    color: "#232323"
                },
                inactiveItems: {
                    labels: {
                        color: "#919191"
                    },
                    markers: {
                        color: "#919191"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: BLACK,
                    background: WHITE,
                    opacity: 0.5
                },
                errorBars: {
                    color: "#232323"
                },
                candlestick: {
                    downColor: "#dedede",
                    line: {
                        color: "#8d8d8d"
                    }
                },
                waterfall: {
                    line: {
                        color: "#8e8e8e"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#8e8e8e"
                    }
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#8e8e8e"
                        }
                    },
                    label: {
                        color: "#232323"
                    },
                    line: {
                        color: "#8e8e8e"
                    }
                }
            },
            seriesColors: ["#ff6800", "#a0a700", "#ff8d00", "#678900", "#ffb53c", "#396000"],
            axisDefaults: {
                line: {
                    color: "#8e8e8e"
                },
                labels: {
                    color: "#232323"
                },
                minorGridLines: {
                    color: "#f0f0f0"
                },
                majorGridLines: {
                    color: "#dfdfdf"
                },
                title: {
                    color: "#232323"
                },
                crosshair: {
                    color: "#8e8e8e"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#8e8e8e"
                        }
                    },
                    label: {
                        color: "#232323"
                    },
                    line: {
                        color: "#8e8e8e"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#ea7001"
            },
            scale: {
                rangePlaceholderColor: "#dedede",

                labels: {
                    color: "#2e2e2e"
                },
                minorTicks: {
                    color: "#2e2e2e"
                },
                majorTicks: {
                    color: "#2e2e2e"
                },
                line: {
                    color: "#2e2e2e"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#e15613"
                },
                connectorDefaults: {
                    fill: {
                        color: "#282828"
                    },
                    stroke: {
                        color: WHITE
                    },
                    hover: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#282828"
                        }
                    }
                },
                content: {
                    color: "#2e2e2e"
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#282828"
                        },
                        hover: {
                            fill: {
                                color: "#282828"
                            },
                            stroke: {
                                color: "#282828"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: "#282828"
                        },
                        fill: {
                            color: "#282828"
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#a7018f"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: "#282828"
                },
                content: {
                    color: "#2e2e2e"
                },
                selection: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#282828"
                        }
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#ff6800", "#edcfba"],
                ["#a0a700", "#dadcba"],
                ["#ff8d00", "#edd7ba"],
                ["#678900", "#cfd6ba"],
                ["#ffb53c", "#eddfc6"],
                ["#396000", "#c6ceba"]
            ]
        }
    });

    registerTheme("silver", {
        chart: {
            title: {
                color: "#4e5968"
            },
            legend: {
                labels: {
                    color: "#4e5968"
                },
                inactiveItems: {
                    labels: {
                        color: "#B1BCC8"
                    },
                    markers: {
                        color: "#B1BCC8"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: "#293135",
                    background: "#eaeaec",
                    opacity: 0.5
                },
                errorBars: {
                    color: "#4e5968"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#4e5968"
                        }
                    },
                    label: {
                        color: "#4e5968"
                    },
                    line: {
                        color: "#4e5968"
                    }
                },
                line: {
                    markers: {
                        background: "#eaeaec"
                    }
                },
                scatter: {
                    markers: {
                        background: "#eaeaec"
                    }
                },
                scatterLine: {
                    markers: {
                        background: "#eaeaec"
                    }
                },
                pie: {
                    connectors: {
                        color: "#A6B1C0"
                    }
                },
                donut: {
                    connectors: {
                        color: "#A6B1C0"
                    }
                },
                waterfall: {
                    line: {
                        color: "#a6b1c0"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#a6b1c0"
                    }
                },
                candlestick: {
                    downColor: "#a6afbe"
                }
            },
            chartArea: {
                background: "#eaeaec"
            },
            seriesColors: ["#007bc3", "#76b800", "#ffae00", "#ef4c00", "#a419b7", "#430B62"],
            axisDefaults: {
                line: {
                    color: "#a6b1c0"
                },
                labels: {
                    color: "#4e5968"
                },
                majorGridLines: {
                    color: "#dcdcdf"
                },
                minorGridLines: {
                    color: "#eeeeef"
                },
                title: {
                    color: "#4e5968"
                },
                crosshair: {
                    color: "#a6b1c0"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#4e5968"
                        }
                    },
                    label: {
                        color: "#4e5968"
                    },
                    line: {
                        color: "#4e5968"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#0879c0"
            },
            scale: {
                rangePlaceholderColor: "#f3f3f4",

                labels: {
                    color: "#515967"
                },
                minorTicks: {
                    color: "#515967"
                },
                majorTicks: {
                    color: "#515967"
                },
                line: {
                    color: "#515967"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#1c82c2"
                },
                connectorDefaults: {
                    fill: {
                        color: "#515967"
                    },
                    stroke: {
                        color: WHITE
                    },
                    hover: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#282828"
                        }
                    }
                },
                content: {
                    color: "#515967"
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#515967"
                        },
                        hover: {
                            fill: {
                                color: "#515967"
                            },
                            stroke: {
                                color: "#515967"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: "#515967"
                        },
                        fill: {
                            color: "#515967"
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#515967"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: "#515967"
                },
                content: {
                    color: "#515967"
                },
                selection: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#515967"
                        }
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#007bc3", "#c2dbea"],
                ["#76b800", "#dae7c3"],
                ["#ffae00", "#f5e5c3"],
                ["#ef4c00", "#f2d2c3"],
                ["#a419b7", "#e3c7e8"],
                ["#430b62", "#d0c5d7"]
            ]
        }
    });

    registerTheme("metro", {
        chart: {
            title: {
                color: "#777777"
            },
            legend: {
                labels: {
                    color: "#777777"
                },
                inactiveItems: {
                    labels: {
                        color: "#CBCBCB"
                    },
                    markers: {
                        color: "#CBCBCB"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: BLACK
                },
                errorBars: {
                    color: "#777777"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#777777"
                        }
                    },
                    label: {
                        color: "#777777"
                    },
                    line: {
                        color: "#777777"
                    }
                },
                candlestick: {
                    downColor: "#c7c7c7",
                    line: {
                        color: "#787878"
                    }
                },
                waterfall: {
                    line: {
                        color: "#c7c7c7"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#c7c7c7"
                    }
                },
                overlay: {
                    gradient: "none"
                },
                border: {
                    _brightness: 1
                }
            },
            seriesColors: ["#8ebc00", "#309b46", "#25a0da", "#ff6900", "#e61e26", "#d8e404", "#16aba9", "#7e51a1", "#313131", "#ed1691"],
            axisDefaults: {
                line: {
                    color: "#c7c7c7"
                },
                labels: {
                    color: "#777777"
                },
                minorGridLines: {
                    color: "#c7c7c7"
                },
                majorGridLines: {
                    color: "#c7c7c7"
                },
                title: {
                    color: "#777777"
                },
                crosshair: {
                    color: "#c7c7c7"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#777777"
                        }
                    },
                    label: {
                        color: "#777777"
                    },
                    line: {
                        color: "#777777"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#8ebc00"
            },
            scale: {
                rangePlaceholderColor: "#e6e6e6",

                labels: {
                    color: "#777"
                },
                minorTicks: {
                    color: "#777"
                },
                majorTicks: {
                    color: "#777"
                },
                line: {
                    color: "#777"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#8ebc00"
                },
                connectorDefaults: {
                    fill: {
                        color: BLACK
                    },
                    stroke: {
                        color: WHITE
                    },
                    hover: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: BLACK
                        }
                    }
                },
                content: {
                    color: "#777"
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#787878"
                        },
                        hover: {
                            fill: {
                                color: "#787878"
                            },
                            stroke: {
                                color: "#787878"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: "#787878"
                        },
                        fill: {
                            color: "#787878"
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#515967"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: "#787878"
                },
                content: {
                    color: "#777"
                },
                selection: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#787878"
                        }
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#8ebc00", "#e8f2cc"],
                ["#309b46", "#d6ebda"],
                ["#25a0da", "#d3ecf8"],
                ["#ff6900", "#ffe1cc"],
                ["#e61e26", "#fad2d4"],
                ["#d8e404", "#f7facd"],
                ["#16aba9", "#d0eeee"],
                ["#7e51a1", "#e5dcec"],
                ["#313131", "#d6d6d6"],
                ["#ed1691", "#fbd0e9"]
            ]
        }
    });

    registerTheme("metroblack", {
        chart: {
            title: {
                color: "#ffffff"
            },
            legend: {
                labels: {
                    color: "#ffffff"
                },
                inactiveItems: {
                    labels: {
                        color: "#797979"
                    },
                    markers: {
                        color: "#797979"
                    }
                }
            },
            seriesDefaults: {
                border: {
                    _brightness: 1
                },
                labels: {
                    color: "#ffffff"
                },
                errorBars: {
                    color: "#ffffff"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#cecece"
                        }
                    },
                    label: {
                        color: "#ffffff"
                    },
                    line: {
                        color: "#cecece"
                    }
                },
                line: {
                    markers: {
                        background: "#0e0e0e"
                    }
                },
                bubble: {
                    opacity: 0.6
                },
                scatter: {
                    markers: {
                        background: "#0e0e0e"
                    }
                },
                scatterLine: {
                    markers: {
                        background: "#0e0e0e"
                    }
                },
                candlestick: {
                    downColor: "#828282",
                    line: {
                        color: "#ffffff"
                    }
                },
                waterfall: {
                    line: {
                        color: "#cecece"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#cecece"
                    }
                },
                overlay: {
                    gradient: "none"
                }
            },
            chartArea: {
                background: "#0e0e0e"
            },
            seriesColors: ["#00aba9", "#309b46", "#8ebc00", "#ff6900", "#e61e26", "#d8e404", "#25a0da", "#7e51a1", "#313131", "#ed1691"],
            axisDefaults: {
                line: {
                    color: "#cecece"
                },
                labels: {
                    color: "#ffffff"
                },
                minorGridLines: {
                    color: "#2d2d2d"
                },
                majorGridLines: {
                    color: "#333333"
                },
                title: {
                    color: "#ffffff"
                },
                crosshair: {
                    color: "#cecece"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#cecece"
                        }
                    },
                    label: {
                        color: "#ffffff"
                    },
                    line: {
                        color: "#cecece"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#00aba9"
            },
            scale: {
                rangePlaceholderColor: "#2d2d2d",

                labels: {
                    color: "#ffffff"
                },
                minorTicks: {
                    color: "#333333"
                },
                majorTicks: {
                    color: "#cecece"
                },
                line: {
                    color: "#cecece"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#00aba9"
                },
                connectorDefaults: {
                    fill: {
                        color: WHITE
                    },
                    stroke: {
                        color: "#0e0e0e"
                    },
                    hover: {
                        fill: {
                            color: "#0e0e0e"
                        },
                        stroke: {
                            color: WHITE
                        }
                    }
                },
                content: {
                    color: WHITE
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: "#0e0e0e"
                        },
                        stroke: {
                            color: "#787878"
                        },
                        hover: {
                            fill: {
                                color: "#787878"
                            },
                            stroke: {
                                color: "#787878"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: WHITE
                        },
                        fill: {
                            color: WHITE
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#787878"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: WHITE
                },
                content: {
                    color: WHITE
                },
                selection: {
                    handles: {
                        fill: {
                            color: "#0e0e0e"
                        },
                        stroke: {
                            color: WHITE
                        }
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#00aba9", "#0b2d2d"],
                ["#309b46", "#152a19"],
                ["#8ebc00", "#28310b"],
                ["#ff6900", "#3e200b"],
                ["#e61e26", "#391113"],
                ["#d8e404", "#36390c"],
                ["#25a0da", "#132b37"],
                ["#7e51a1", "#241b2b"],
                ["#313131", "#151515"],
                ["#ed1691", "#3b1028"]
            ]
        }
    });

    registerTheme("moonlight", {
        chart: {
            title: {
                color: "#ffffff"
            },
            legend: {
                labels: {
                    color: "#ffffff"
                },
                inactiveItems: {
                    labels: {
                        color: "#A1A7AB"
                    },
                    markers: {
                        color: "#A1A7AB"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: "#ffffff"
                },
                errorBars: {
                    color: "#ffffff"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#8c909e"
                        }
                    },
                    label: {
                        color: "#ffffff"
                    },
                    line: {
                        color: "#8c909e"
                    }
                },
                pie: {
                    overlay: {
                        gradient: "sharpBevel"
                    }
                },
                donut: {
                    overlay: {
                        gradient: "sharpGlass"
                    }
                },
                line: {
                    markers: {
                        background: "#212a33"
                    }
                },
                bubble: {
                    opacity: 0.6
                },
                scatter: {
                    markers: {
                        background: "#212a33"
                    }
                },
                scatterLine: {
                    markers: {
                        background: "#212a33"
                    }
                },
                area: {
                    opacity: 0.3
                },
                candlestick: {
                    downColor: "#757d87",
                    line: {
                        color: "#ea9d06"
                    },
                    border: {
                        _brightness: 1.5,
                        opacity: 1
                    },
                    highlight: {
                        border: {
                            color: WHITE,
                            opacity: 0.2
                        }
                    }
                },
                waterfall: {
                    line: {
                        color: "#8c909e"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#8c909e"
                    }
                },
                ohlc: {
                    line: {
                        color: "#ea9d06"
                    }
                }
            },
            chartArea: {
                background: "#212a33"
            },
            seriesColors: ["#ffca08", "#ff710f", "#ed2e24", "#ff9f03", "#e13c02", "#a00201"],
            axisDefaults: {
                line: {
                    color: "#8c909e"
                },
                minorTicks: {
                    color: "#8c909e"
                },
                majorTicks: {
                    color: "#8c909e"
                },
                labels: {
                    color: "#ffffff"
                },
                majorGridLines: {
                    color: "#3e424d"
                },
                minorGridLines: {
                    color: "#2f3640"
                },
                title: {
                    color: "#ffffff"
                },
                crosshair: {
                    color: "#8c909e"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#8c909e"
                        }
                    },
                    label: {
                        color: "#ffffff"
                    },
                    line: {
                        color: "#8c909e"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#f4af03"
            },
            scale: {
                rangePlaceholderColor: "#2f3640",

                labels: {
                    color: WHITE
                },
                minorTicks: {
                    color: "#8c909e"
                },
                majorTicks: {
                    color: "#8c909e"
                },
                line: {
                    color: "#8c909e"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#f3ae03"
                },
                connectorDefaults: {
                    fill: {
                        color: WHITE
                    },
                    stroke: {
                        color: "#414550"
                    },
                    hover: {
                        fill: {
                            color: "#414550"
                        },
                        stroke: {
                            color: WHITE
                        }
                    }
                },
                content: {
                    color: WHITE
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: "#414550"
                        },
                        stroke: {
                            color: WHITE
                        },
                        hover: {
                            fill: {
                                color: WHITE
                            },
                            stroke: {
                                color: WHITE
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: WHITE
                        },
                        fill: {
                            color: WHITE
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: WHITE
                }
            },
            connectionDefaults: {
                stroke: {
                    color: WHITE
                },
                content: {
                    color: WHITE
                },
                selection: {
                    handles: {
                        fill: {
                            color: "#414550"
                        },
                        stroke: {
                            color: WHITE
                        }
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#ffca08", "#4e4b2b"],
                ["#ff710f", "#4e392d"],
                ["#ed2e24", "#4b2c31"],
                ["#ff9f03", "#4e422a"],
                ["#e13c02", "#482e2a"],
                ["#a00201", "#3b232a"]
            ]
        }
    });

    registerTheme("uniform", {
        chart: {
            title: {
                color: "#686868"
            },
            legend: {
                labels: {
                    color: "#686868"
                },
                inactiveItems: {
                    labels: {
                        color: "#B6B6B6"
                    },
                    markers: {
                        color: "#B6B6B6"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: "#686868"
                },
                errorBars: {
                    color: "#686868"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#9e9e9e"
                        }
                    },
                    label: {
                        color: "#686868"
                    },
                    line: {
                        color: "#9e9e9e"
                    }
                },
                pie: {
                    overlay: {
                        gradient: "sharpBevel"
                    }
                },
                donut: {
                    overlay: {
                        gradient: "sharpGlass"
                    }
                },
                line: {
                    markers: {
                        background: "#ffffff"
                    }
                },
                bubble: {
                    opacity: 0.6
                },
                scatter: {
                    markers: {
                        background: "#ffffff"
                    }
                },
                scatterLine: {
                    markers: {
                        background: "#ffffff"
                    }
                },
                area: {
                    opacity: 0.3
                },
                candlestick: {
                    downColor: "#cccccc",
                    line: {
                        color: "#cccccc"
                    },
                    border: {
                        _brightness: 1.5,
                        opacity: 1
                    },
                    highlight: {
                        border: {
                            color: "#cccccc",
                            opacity: 0.2
                        }
                    }
                },
                waterfall: {
                    line: {
                        color: "#9e9e9e"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#9e9e9e"
                    }
                },
                ohlc: {
                    line: {
                        color: "#cccccc"
                    }
                }
            },
            chartArea: {
                background: "#ffffff"
            },
            seriesColors: ["#527aa3", "#6f91b3", "#8ca7c2", "#a8bdd1", "#c5d3e0", "#e2e9f0"],
            axisDefaults: {
                line: {
                    color: "#9e9e9e"
                },
                minorTicks: {
                    color: "#aaaaaa"
                },
                majorTicks: {
                    color: "#888888"
                },
                labels: {
                    color: "#686868"
                },
                majorGridLines: {
                    color: "#dadada"
                },
                minorGridLines: {
                    color: "#e7e7e7"
                },
                title: {
                    color: "#686868"
                },
                crosshair: {
                    color: "#9e9e9e"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#9e9e9e"
                        }
                    },
                    label: {
                        color: "#686868"
                    },
                    line: {
                        color: "#9e9e9e"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#527aa3"
            },
            scale: {
                rangePlaceholderColor: "#e7e7e7",

                labels: {
                    color: "#686868"
                },
                minorTicks: {
                    color: "#aaaaaa"
                },
                majorTicks: {
                    color: "#888888"
                },
                line: {
                    color: "#9e9e9e"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#d1d1d1"
                },
                connectorDefaults: {
                    fill: {
                        color: "#686868"
                    },
                    stroke: {
                        color: WHITE
                    },
                    hover: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#686868"
                        }
                    }
                },
                content: {
                    color: "#686868"
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#686868"
                        },
                        hover: {
                            fill: {
                                color: "#686868"
                            },
                            stroke: {
                                color: "#686868"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: "#686868"
                        },
                        fill: {
                            color: "#686868"
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#686868"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: "#686868"
                },
                content: {
                    color: "#686868"
                },
                selection: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#686868"
                        }
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#527aa3", "#d0d8e1"],
                ["#6f91b3", "#d6dde4"],
                ["#8ca7c2", "#dce1e7"],
                ["#a8bdd1", "#e2e6ea"],
                ["#c5d3e0", "#e7eaed"],
                ["#e2e9f0", "#edeff0"]
            ]
        }
    });

    registerTheme("bootstrap", {
        chart: {
            title: {
                color: "#333333"
            },
            legend: {
                labels: {
                    color: "#333333"
                },
                inactiveItems: {
                    labels: {
                        color: "#999999"
                    },
                    markers: {
                        color: "#9A9A9A"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: "#333333"
                },
                overlay: {
                    gradient: "none"
                },
                errorBars: {
                    color: "#343434"
                },
                notes: {
                    icon: {
                        background: "#000000",
                        border: {
                            color: "#000000"
                        }
                    },
                    label: {
                        color: "#333333"
                    },
                    line: {
                        color: "#000000"
                    }
                },
                pie: {
                    overlay: {
                        gradient: "none"
                    }
                },
                donut: {
                    overlay: {
                        gradient: "none"
                    }
                },
                line: {
                    markers: {
                        background: "#ffffff"
                    }
                },
                bubble: {
                    opacity: 0.6
                },
                scatter: {
                    markers: {
                        background: "#ffffff"
                    }
                },
                scatterLine: {
                    markers: {
                        background: "#ffffff"
                    }
                },
                area: {
                    opacity: 0.8
                },
                candlestick: {
                    downColor: "#d0d0d0",
                    line: {
                        color: "#333333"
                    },
                    border: {
                        _brightness: 1.5,
                        opacity: 1
                    },
                    highlight: {
                        border: {
                            color: "#b8b8b8",
                            opacity: 0.2
                        }
                    }
                },
                waterfall: {
                    line: {
                        color: "#cccccc"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#cccccc"
                    }
                },
                ohlc: {
                    line: {
                        color: "#333333"
                    }
                }
            },
            chartArea: {
                background: "#ffffff"
            },
            seriesColors: ["#428bca", "#5bc0de", "#5cb85c", "#f2b661", "#e67d4a", "#da3b36"],
            axisDefaults: {
                line: {
                    color: "#cccccc"
                },
                minorTicks: {
                    color: "#ebebeb"
                },
                majorTicks: {
                    color: "#cccccc"
                },
                labels: {
                    color: "#333333"
                },
                majorGridLines: {
                    color: "#cccccc"
                },
                minorGridLines: {
                    color: "#ebebeb"
                },
                title: {
                    color: "#333333"
                },
                crosshair: {
                    color: "#000000"
                },
                notes: {
                    icon: {
                        background: "#000000",
                        border: {
                            color: "#000000"
                        }
                    },
                    label: {
                        color: "#ffffff"
                    },
                    line: {
                        color: "#000000"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#428bca"
            },
            scale: {
                rangePlaceholderColor: "#cccccc",
                labels: {
                    color: "#333333"
                },
                minorTicks: {
                    color: "#ebebeb"
                },
                majorTicks: {
                    color: "#cccccc"
                },
                line: {
                    color: "#cccccc"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#428bca"
                },
                connectorDefaults: {
                    fill: {
                        color: "#333333"
                    },
                    stroke: {
                        color: WHITE
                    },
                    hover: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#333333"
                        }
                    }
                },
                content: {
                    color: "#333333"
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#333333"
                        },
                        hover: {
                            fill: {
                                color: "#333333"
                            },
                            stroke: {
                                color: "#333333"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: "#333333"
                        },
                        fill: {
                            color: "#333333"
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#333333"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: "#c4c4c4"
                },
                content: {
                    color: "#333333"
                },
                selection: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#333333"
                        }
                    },
                    stroke: {
                        color: "#333333"
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#428bca", "#d1e0ec"],
                ["#5bc0de", "#d6eaf0"],
                ["#5cb85c", "#d6e9d6"],
                ["#5cb85c", "#f4e8d7"],
                ["#e67d4a", "#f2ddd3"],
                ["#da3b36", "#f0d0cf"]
            ]
        }
    });

    registerTheme("flat", {
        chart: {
            title: {
                color: "#4c5356"
            },
            legend: {
                labels: {
                    color: "#4c5356"
                },
                inactiveItems: {
                    labels: {
                        color: "#CBCBCB"
                    },
                    markers: {
                        color: "#CBCBCB"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: "#4c5356"
                },
                errorBars: {
                    color: "#4c5356"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#cdcdcd"
                        }
                    },
                    label: {
                        color: "#4c5356"
                    },
                    line: {
                        color: "#cdcdcd"
                    }
                },
                candlestick: {
                    downColor: "#c7c7c7",
                    line: {
                        color: "#787878"
                    }
                },
                area: {
                    opacity: 0.9
                },
                waterfall: {
                    line: {
                        color: "#cdcdcd"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#cdcdcd"
                    }
                },
                overlay: {
                    gradient: "none"
                },
                border: {
                    _brightness: 1
                }
            },
            seriesColors: ["#10c4b2", "#ff7663", "#ffb74f", "#a2df53", "#1c9ec4", "#ff63a5", "#1cc47b"],
            axisDefaults: {
                line: {
                    color: "#cdcdcd"
                },
                labels: {
                    color: "#4c5356"
                },
                minorGridLines: {
                    color: "#cdcdcd"
                },
                majorGridLines: {
                    color: "#cdcdcd"
                },
                title: {
                    color: "#4c5356"
                },
                crosshair: {
                    color: "#cdcdcd"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#cdcdcd"
                        }
                    },
                    label: {
                        color: "#4c5356"
                    },
                    line: {
                        color: "#cdcdcd"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#10c4b2"
            },
            scale: {
                rangePlaceholderColor: "#cdcdcd",

                labels: {
                    color: "#4c5356"
                },
                minorTicks: {
                    color: "#4c5356"
                },
                majorTicks: {
                    color: "#4c5356"
                },
                line: {
                    color: "#4c5356"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#10c4b2"
                },
                connectorDefaults: {
                    fill: {
                        color: "#363940"
                    },
                    stroke: {
                        color: WHITE
                    },
                    hover: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#363940"
                        }
                    }
                },
                content: {
                    color: "#4c5356"
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#363940"
                        },
                        hover: {
                            fill: {
                                color: "#363940"
                            },
                            stroke: {
                                color: "#363940"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: "#363940"
                        },
                        fill: {
                            color: "#363940"
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#363940"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: "#cdcdcd"
                },
                content: {
                    color: "#4c5356"
                },
                selection: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#363940"
                        }
                    },
                    stroke: {
                        color: "#363940"
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#10c4b2", "#cff3f0"],
                ["#ff7663", "#ffe4e0"],
                ["#ffb74f", "#fff1dc"],
                ["#a2df53", "#ecf9dd"],
                ["#1c9ec4", "#d2ecf3"],
                ["#ff63a5", "#ffe0ed"],
                ["#1cc47b", "#d2f3e5"]
            ]
        }
    });


    registerTheme("material", {
        chart: {
            title: {
                color: "#444444"
            },
            legend: {
                labels: {
                    color: "#444444"
                },
                inactiveItems: {
                    labels: {
                        color: "#CBCBCB"
                    },
                    markers: {
                        color: "#CBCBCB"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: "#444444"
                },
                errorBars: {
                    color: "#444444"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#e5e5e5"
                        }
                    },
                    label: {
                        color: "#444444"
                    },
                    line: {
                        color: "#e5e5e5"
                    }
                },
                candlestick: {
                    downColor: "#c7c7c7",
                    line: {
                        color: "#787878"
                    }
                },
                area: {
                    opacity: 0.9
                },
                waterfall: {
                    line: {
                        color: "#e5e5e5"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#e5e5e5"
                    }
                },
                overlay: {
                    gradient: "none"
                },
                border: {
                    _brightness: 1
                }
            },
            seriesColors: ["#3f51b5", "#03a9f4", "#4caf50", "#f9ce1d", "#ff9800", "#ff5722"],
            axisDefaults: {
                line: {
                    color: "#e5e5e5"
                },
                labels: {
                    color: "#444444"
                },
                minorGridLines: {
                    color: "#e5e5e5"
                },
                majorGridLines: {
                    color: "#e5e5e5"
                },
                title: {
                    color: "#444444"
                },
                crosshair: {
                    color: "#7f7f7f"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#e5e5e5"
                        }
                    },
                    label: {
                        color: "#444444"
                    },
                    line: {
                        color: "#e5e5e5"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#3f51b5"
            },
            scale: {
                rangePlaceholderColor: "#e5e5e5",

                labels: {
                    color: "#444444"
                },
                minorTicks: {
                    color: "#444444"
                },
                majorTicks: {
                    color: "#444444"
                },
                line: {
                    color: "#444444"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#3f51b5"
                },
                connectorDefaults: {
                    fill: {
                        color: "#7f7f7f"
                    },
                    stroke: {
                        color: WHITE
                    },
                    hover: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#7f7f7f"
                        }
                    }
                },
                content: {
                    color: "#444444"
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#444444"
                        },
                        hover: {
                            fill: {
                                color: "#444444"
                            },
                            stroke: {
                                color: "#444444"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: "#444444"
                        },
                        fill: {
                            color: "#444444"
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#444444"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: "#7f7f7f"
                },
                content: {
                    color: "#444444"
                },
                selection: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#444444"
                        }
                    },
                    stroke: {
                        color: "#444444"
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#3f51b5", "#cff3f0"],
                ["#03a9f4", "#e5f6fe"],
                ["#4caf50", "#edf7ed"],
                ["#f9ce1d", "#fefae8"],
                ["#ff9800", "#fff4e5"],
                ["#ff5722", "#ffeee8"]
            ]
        }
    });

    registerTheme("materialblack", {
        chart: {
            title: {
                color: "#fff"
            },
            legend: {
                labels: {
                    color: "#fff"
                },
                inactiveItems: {
                    labels: {
                        color: "#CBCBCB"
                    },
                    markers: {
                        color: "#CBCBCB"
                    }
                }
            },
            seriesDefaults: {
                labels: {
                    color: "#fff"
                },
                errorBars: {
                    color: "#fff"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#e5e5e5"
                        }
                    },
                    label: {
                        color: "#fff"
                    },
                    line: {
                        color: "#e5e5e5"
                    }
                },
                candlestick: {
                    downColor: "#c7c7c7",
                    line: {
                        color: "#787878"
                    }
                },
                area: {
                    opacity: 0.9
                },
                waterfall: {
                    line: {
                        color: "#4d4d4d"
                    }
                },
                horizontalWaterfall: {
                    line: {
                        color: "#4d4d4d"
                    }
                },
                overlay: {
                    gradient: "none"
                },
                border: {
                    _brightness: 1
                }
            },
            chartArea: {
                background: "#1c1c1c"
            },
            seriesColors: ["#3f51b5", "#03a9f4", "#4caf50", "#f9ce1d", "#ff9800", "#ff5722"],
            axisDefaults: {
                line: {
                    color: "#4d4d4d"
                },
                labels: {
                    color: "#fff"
                },
                minorGridLines: {
                    color: "#4d4d4d"
                },
                majorGridLines: {
                    color: "#4d4d4d"
                },
                title: {
                    color: "#fff"
                },
                crosshair: {
                    color: "#7f7f7f"
                },
                notes: {
                    icon: {
                        background: "transparent",
                        border: {
                            color: "#4d4d4d"
                        }
                    },
                    label: {
                        color: "#fff"
                    },
                    line: {
                        color: "#4d4d4d"
                    }
                }
            }
        },
        gauge: {
            pointer: {
                color: "#3f51b5"
            },
            scale: {
                rangePlaceholderColor: "#4d4d4d",

                labels: {
                    color: "#fff"
                },
                minorTicks: {
                    color: "#fff"
                },
                majorTicks: {
                    color: "#fff"
                },
                line: {
                    color: "#fff"
                }
            }
        },
        diagram: {
            shapeDefaults: {
                fill: {
                    color: "#3f51b5"
                },
                connectorDefaults: {
                    fill: {
                        color: "#7f7f7f"
                    },
                    stroke: {
                        color: WHITE
                    },
                    hover: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#7f7f7f"
                        }
                    }
                },
                content: {
                    color: "#fff"
                }
            },
            editable: {
                resize: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#fff"
                        },
                        hover: {
                            fill: {
                                color: "#fff"
                            },
                            stroke: {
                                color: "#fff"
                            }
                        }
                    }
                },
                rotate: {
                    thumb: {
                        stroke: {
                            color: "#fff"
                        },
                        fill: {
                            color: "#fff"
                        }
                    }
                }
            },
            selectable: {
                stroke: {
                    color: "#fff"
                }
            },
            connectionDefaults: {
                stroke: {
                    color: "#7f7f7f"
                },
                content: {
                    color: "#fff"
                },
                selection: {
                    handles: {
                        fill: {
                            color: WHITE
                        },
                        stroke: {
                            color: "#fff"
                        }
                    },
                    stroke: {
                        color: "#fff"
                    }
                }
            }
        },
        treeMap: {
            colors: [
                ["#3f51b5", "#cff3f0"],
                ["#03a9f4", "#e5f6fe"],
                ["#4caf50", "#edf7ed"],
                ["#f9ce1d", "#fefae8"],
                ["#ff9800", "#fff4e5"],
                ["#ff5722", "#ffeee8"]
            ]
        }
    });

})(window.kendo.jQuery);





(function ($, undefined) {
    // Imports ================================================================
    var each = $.each,
        isArray = $.isArray,
        map = $.map,
        math = Math,
        noop = $.noop,
        extend = $.extend,
        proxy = $.proxy,

        kendo = window.kendo,
        Class = kendo.Class,
        Observable = kendo.Observable,
        DataSource = kendo.data.DataSource,
        Widget = kendo.ui.Widget,
        deepExtend = kendo.deepExtend,
        getter = kendo.getter,
        isFn = kendo.isFunction,
        template = kendo.template,

        dataviz = kendo.dataviz,
        Axis = dataviz.Axis,
        AxisLabel = dataviz.AxisLabel,
        Box2D = dataviz.Box2D,
        BoxElement = dataviz.BoxElement,
        ChartElement = dataviz.ChartElement,
        Color = kendo.drawing.Color,
        CurveProcessor = dataviz.CurveProcessor,
        FloatElement = dataviz.FloatElement,
        Note = dataviz.Note,
        LogarithmicAxis = dataviz.LogarithmicAxis,
        NumericAxis = dataviz.NumericAxis,
        Point2D = dataviz.Point2D,
        RootElement = dataviz.RootElement,
        Ring = dataviz.Ring,
        ShapeElement = dataviz.ShapeElement,
        ShapeBuilder = dataviz.ShapeBuilder,
        Text = dataviz.Text,
        TextBox = dataviz.TextBox,
        Title = dataviz.Title,
        alignPathToPixel = dataviz.alignPathToPixel,
        autoFormat = dataviz.autoFormat,
        dateComparer = dataviz.dateComparer,
        getSpacing = dataviz.getSpacing,
        inArray = dataviz.inArray,
        interpolate = dataviz.interpolateValue,
        mwDelta = dataviz.mwDelta,
        round = dataviz.round,

        util = kendo.util,
        append = util.append,
        defined = util.defined,
        last = util.last,
        limitValue = util.limitValue,
        sparseArrayLimits = util.sparseArrayLimits,
        sparseArrayMin = util.sparseArrayMin,
        sparseArrayMax = util.sparseArrayMax,
        renderTemplate = util.renderTemplate,
        valueOrDefault = util.valueOrDefault,

        geom = dataviz.geometry,
        draw = dataviz.drawing;

    // Constants ==============================================================
    var NS = ".kendoChart",
        ABOVE = "above",
        AREA = "area",
        AUTO = "auto",
        FIT = "fit",
        AXIS_LABEL_CLICK = dataviz.AXIS_LABEL_CLICK,
        BAR = "bar",
        BAR_BORDER_BRIGHTNESS = 0.8,
        BELOW = "below",
        BLACK = "#000",
        BOTH = "both",
        BOTTOM = "bottom",
        BOX_PLOT = "boxPlot",
        BUBBLE = "bubble",
        BULLET = "bullet",
        CANDLESTICK = "candlestick",
        CATEGORY = "category",
        CENTER = "center",
        CHANGE = "change",
        CIRCLE = "circle",
        CONTEXTMENU_NS = "contextmenu" + NS,
        CLIP = dataviz.CLIP,
        COLOR = "color",
        COLUMN = "column",
        COORD_PRECISION = dataviz.COORD_PRECISION,
        CROSS = "cross",
        CSS_PREFIX = "k-",
        CUSTOM = "custom",
        DATABOUND = "dataBound",
        DATE = "date",
        DAYS = "days",
        DEFAULT_FONT = dataviz.DEFAULT_FONT,
        DEFAULT_HEIGHT = dataviz.DEFAULT_HEIGHT,
        DEFAULT_PRECISION = dataviz.DEFAULT_PRECISION,
        DEFAULT_WIDTH = dataviz.DEFAULT_WIDTH,
        DEFAULT_ERROR_BAR_WIDTH = 4,
        DONUT = "donut",
        DONUT_SECTOR_ANIM_DELAY = 50,
        DRAG = "drag",
        DRAG_END = "dragEnd",
        DRAG_START = "dragStart",
        ERROR_LOW_FIELD = "errorLow",
        ERROR_HIGH_FIELD = "errorHigh",
        X_ERROR_LOW_FIELD = "xErrorLow",
        X_ERROR_HIGH_FIELD = "xErrorHigh",
        Y_ERROR_LOW_FIELD = "yErrorLow",
        Y_ERROR_HIGH_FIELD = "yErrorHigh",
        FADEIN = "fadeIn",
        FIRST = "first",
        FROM = "from",
        FUNNEL = "funnel",
        GLASS = "glass",
        HORIZONTAL = "horizontal",
        HORIZONTAL_WATERFALL = "horizontalWaterfall",
        HOURS = "hours",
        INITIAL_ANIMATION_DURATION = dataviz.INITIAL_ANIMATION_DURATION,
        INSIDE_BASE = "insideBase",
        INSIDE_END = "insideEnd",
        INTERPOLATE = "interpolate",
        LEFT = "left",
        LEGEND_ITEM_CLICK = "legendItemClick",
        LEGEND_ITEM_HOVER = "legendItemHover",
        LINE = "line",
        LINE_MARKER_SIZE = 8,
        LINEAR = "linear",
        LOGARITHMIC = "log",
        MAX = "max",
        MAX_EXPAND_DEPTH = 5,
        MAX_VALUE = Number.MAX_VALUE,
        MIN = "min",
        MIN_VALUE = -Number.MAX_VALUE,
        MINUTES = "minutes",
        MONTHS = "months",
        MOUSELEAVE_NS = "mouseleave" + NS,
        MOUSEMOVE_TRACKING = "mousemove.tracking",
        MOUSEOVER_NS = "mouseover" + NS,
        MOUSEOUT_NS = "mouseout" + NS,
        MOUSEMOVE_NS = "mousemove" + NS,
        MOUSEMOVE_DELAY = 20,
        MOUSEWHEEL_DELAY = 150,
        MOUSEWHEEL_NS = "DOMMouseScroll" + NS + " mousewheel" + NS,
        NOTE_CLICK = dataviz.NOTE_CLICK,
        NOTE_HOVER = dataviz.NOTE_HOVER,
        NOTE_TEXT = "noteText",
        OBJECT = "object",
        OHLC = "ohlc",
        OUTSIDE_END = "outsideEnd",
        OUTLINE_SUFFIX = "_outline",
        PIE = "pie",
        PIE_SECTOR_ANIM_DELAY = 70,
        PLOT_AREA_CLICK = "plotAreaClick",
        POINTER = "pointer",
        RANGE_BAR = "rangeBar",
        RANGE_COLUMN = "rangeColumn",
        RENDER = "render",
        RIGHT = "right",
        ROUNDED_BEVEL = "roundedBevel",
        ROUNDED_GLASS = "roundedGlass",
        SCATTER = "scatter",
        SCATTER_LINE = "scatterLine",
        SECONDS = "seconds",
        SELECT_START = "selectStart",
        SELECT = "select",
        SELECT_END = "selectEnd",
        SERIES_CLICK = "seriesClick",
        SERIES_HOVER = "seriesHover",
        STEP = "step",
        SMOOTH = "smooth",
        STD_ERR = "stderr",
        STD_DEV = "stddev",
        STRING = "string",
        SUMMARY_FIELD = "summary",
        TIME_PER_SECOND = 1000,
        TIME_PER_MINUTE = 60 * TIME_PER_SECOND,
        TIME_PER_HOUR = 60 * TIME_PER_MINUTE,
        TIME_PER_DAY = 24 * TIME_PER_HOUR,
        TIME_PER_WEEK = 7 * TIME_PER_DAY,
        TIME_PER_MONTH = 31 * TIME_PER_DAY,
        TIME_PER_YEAR = 365 * TIME_PER_DAY,
        TIME_PER_UNIT = {
            "years": TIME_PER_YEAR,
            "months": TIME_PER_MONTH,
            "weeks": TIME_PER_WEEK,
            "days": TIME_PER_DAY,
            "hours": TIME_PER_HOUR,
            "minutes": TIME_PER_MINUTE,
            "seconds": TIME_PER_SECOND
        },
        TO = "to",
        TOP = "top",
        TOOLTIP_ANIMATION_DURATION = 150,
        TOOLTIP_OFFSET = 5,
        TOOLTIP_SHOW_DELAY = 100,
        TOOLTIP_HIDE_DELAY = 100,
        TOOLTIP_INVERSE = "chart-tooltip-inverse",
        VALUE = "value",
        VERTICAL = "vertical",
        VERTICAL_AREA = "verticalArea",
        VERTICAL_BULLET = "verticalBullet",
        VERTICAL_LINE = "verticalLine",
        WATERFALL = "waterfall",
        WEEKS = "weeks",
        WHITE = "#fff",
        X = "x",
        Y = "y",
        YEARS = "years",
        ZERO = "zero",
        ZOOM_ACCELERATION = 3,
        ZOOM_START = "zoomStart",
        ZOOM = "zoom",
        ZOOM_END = "zoomEnd",
        BASE_UNITS = [
            SECONDS, MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS
        ],
        EQUALLY_SPACED_SERIES = [
            BAR, COLUMN, OHLC, CANDLESTICK, BOX_PLOT, BULLET, RANGE_COLUMN, RANGE_BAR, WATERFALL, HORIZONTAL_WATERFALL
        ];

    var DateLabelFormats = {
        seconds: "HH:mm:ss",
        minutes: "HH:mm",
        hours: "HH:mm",
        days: "M/d",
        weeks: "M/d",
        months: "MMM 'yy",
        years: "yyyy"
    };

    // Chart ==================================================================
    var Chart = Widget.extend({
        init: function (element, userOptions) {
            var chart = this,
                options,
                dataSource;

            kendo.destroy(element);

            Widget.fn.init.call(chart, element);

            chart.element
                .addClass(CSS_PREFIX + this.options.name.toLowerCase())
                .css("position", "relative");

            if (userOptions) {
                dataSource = userOptions.dataSource;
                userOptions.dataSource = undefined;
            }

            options = deepExtend({}, chart.options, userOptions);
            chart._originalOptions = deepExtend({}, options);
            chart._initTheme(options);

            chart._initSurface();

            chart.bind(chart.events, chart.options);

            chart.wrapper = chart.element;

            if (userOptions) {
                userOptions.dataSource = dataSource;
            }

            chart._initDataSource(userOptions);

            kendo.notify(chart, dataviz.ui);
        },

        _initTheme: function (options) {
            var chart = this,
                themes = dataviz.ui.themes || {},
                themeName = options.theme,
                theme = themes[themeName] || themes[themeName.toLowerCase()],
                themeOptions = themeName && theme ? theme.chart : {},
                seriesCopies = [],
                series = options.series || [],
                i;

            for (i = 0; i < series.length; i++) {
                seriesCopies.push($.extend({}, series[i]));
            }
            options.series = seriesCopies;

            resolveAxisAliases(options);
            chart._applyDefaults(options, themeOptions);

            // Clean up default if not overriden by data attributes
            if (options.seriesColors === null) {
                options.seriesColors = undefined;
            }

            chart.options = deepExtend({}, themeOptions, options);
            applySeriesColors(chart.options);
        },

        _initDataSource: function (userOptions) {
            var chart = this,
                dataSource = (userOptions || {}).dataSource;

            chart._dataChangeHandler = proxy(chart._onDataChanged, chart);

            chart.dataSource = DataSource
                .create(dataSource)
                .bind(CHANGE, chart._dataChangeHandler);

            chart._bindCategories();

            if (dataSource) {
                chart._hasDataSource = true;
            }

            chart._redraw();
            chart._attachEvents();

            if (dataSource) {
                if (chart.options.autoBind) {
                    chart.dataSource.fetch();
                }
            }
        },

        setDataSource: function (dataSource) {
            var chart = this;

            chart.dataSource.unbind(CHANGE, chart._dataChangeHandler);
            chart.dataSource = dataSource = DataSource.create(dataSource);
            chart._hasDataSource = true;
            chart._hasData = false;

            dataSource.bind(CHANGE, chart._dataChangeHandler);

            if (chart.options.autoBind) {
                dataSource.fetch();
            }
        },

        events: [
            DATABOUND,
            SERIES_CLICK,
            SERIES_HOVER,
            AXIS_LABEL_CLICK,
            LEGEND_ITEM_CLICK,
            LEGEND_ITEM_HOVER,
            PLOT_AREA_CLICK,
            DRAG_START,
            DRAG,
            DRAG_END,
            ZOOM_START,
            ZOOM,
            ZOOM_END,
            SELECT_START,
            SELECT,
            SELECT_END,
            NOTE_CLICK,
            NOTE_HOVER,
            RENDER
        ],

        items: function () {
            return $();
        },

        options: {
            name: "Chart",
            renderAs: "",
            theme: "default",
            chartArea: {},
            legend: {
                visible: true,
                labels: {}
            },
            categoryAxis: {},
            autoBind: true,
            seriesDefaults: {
                type: COLUMN,
                data: [],
                highlight: {
                    visible: true
                },
                labels: {},
                negativeValues: {
                    visible: false
                }
            },
            series: [],
            seriesColors: null,
            tooltip: {
                visible: false
            },
            transitions: true,
            valueAxis: {},
            plotArea: {},
            title: {},
            xAxis: {},
            yAxis: {},
            panes: [{}]
        },

        refresh: function () {
            var chart = this;

            chart._applyDefaults(chart.options);

            applySeriesColors(chart.options);

            chart._bindSeries();
            chart._bindCategories();

            chart.trigger(DATABOUND);
            chart._redraw();
        },

        getSize: function () {
            return kendo.dimensions(this.element);
        },

        _resize: function () {
            var t = this.options.transitions;
            this.options.transitions = false;

            this._redraw();

            this.options.transitions = t;
        },

        redraw: function (paneName) {
            var chart = this,
                pane,
                plotArea;

            chart._applyDefaults(chart.options);
            applySeriesColors(chart.options);

            if (paneName) {
                plotArea = chart._model._plotArea;
                pane = plotArea.findPane(paneName);
                plotArea.redraw(pane);
            } else {
                chart._redraw();
            }
        },

        _initSurface: function () {
            var surface = this.surface;
            var wrap = this._surfaceWrap();
            if (!surface || surface.options.type !== this.options.renderAs) {
                if (surface) {
                    surface.destroy();
                }

                this.surface = draw.Surface.create(wrap, {
                    type: this.options.renderAs
                });
            } else {
                this.surface.clear();
            }

            var chartArea = this.options.chartArea;
            if (chartArea.width) {
                wrap.css("width", chartArea.width);
            }
            if (chartArea.height) {
                wrap.css("height", chartArea.height);
            }
        },

        _surfaceWrap: function () {
            return this.element;
        },

        _redraw: function () {
            var chart = this,
                model = chart._getModel(),
                view;

            chart._destroyView();

            chart._model = model;
            chart._plotArea = model._plotArea;

            model.renderVisual();

            if (this.options.transitions !== false) {
                model.traverse(function (element) {
                    if (element.animation) {
                        element.animation.setup();
                    }
                });
            }

            chart._initSurface();
            chart.surface.draw(model.visual);

            if (this.options.transitions !== false) {
                model.traverse(function (element) {
                    if (element.animation) {
                        element.animation.play();
                    }
                });
            }

            chart._tooltip = chart._createTooltip();
            chart._highlight = new Highlight(view);
            chart._setupSelection();

            if (!chart._hasDataSource || chart._hasData || !chart.options.autoBind) {
                chart.trigger(RENDER);
            }
        },

        exportVisual: function () {
            var model = this._getModel();
            model.renderVisual();

            return model.visual;
        },

        _sharedTooltip: function () {
            var chart = this,
                options = chart.options;

            return chart._plotArea instanceof CategoricalPlotArea && options.tooltip.shared;
        },

        _createTooltip: function () {
            var chart = this,
                options = chart.options,
                element = chart.element,
                tooltip;

            if (chart._sharedTooltip()) {
                tooltip = new SharedTooltip(element, chart._plotArea, options.tooltip);
            } else {
                tooltip = new Tooltip(element, options.tooltip);
            }

            return tooltip;
        },

        _applyDefaults: function (options, themeOptions) {
            applyAxisDefaults(options, themeOptions);
            applySeriesDefaults(options, themeOptions);
        },

        _getModel: function () {
            var chart = this,
                options = chart.options,
                model = new RootElement(chart._modelOptions()),
                plotArea;

            model.chart = chart;

            Title.buildTitle(options.title, model);

            plotArea = model._plotArea = chart._createPlotArea();
            if (options.legend.visible) {
                model.append(new Legend(plotArea.options.legend));
            }
            model.append(plotArea);
            model.reflow();

            return model;
        },

        _modelOptions: function () {
            var chart = this,
                options = chart.options,
                element = chart.element,
                height = math.floor(element.height()),
                width = math.floor(element.width());

            chart._size = null;

            return deepExtend({
                width: width || DEFAULT_WIDTH,
                height: height || DEFAULT_HEIGHT,
                transitions: options.transitions
            }, options.chartArea);
        },

        _createPlotArea: function (skipSeries) {
            var chart = this,
                options = chart.options;

            return PlotAreaFactory.current.create(skipSeries ? [] : options.series, options);
        },

        _setupSelection: function () {
            var chart = this,
                plotArea = chart._plotArea,
                axes = plotArea.axes,
                selections = chart._selections = [],
                selection, i, axis,
                min, max, options;

            if (!chart._selectStartHandler) {
                chart._selectStartHandler = proxy(chart._selectStart, chart);
                chart._selectHandler = proxy(chart._select, chart);
                chart._selectEndHandler = proxy(chart._selectEnd, chart);
            }

            for (i = 0; i < axes.length; i++) {
                axis = axes[i];
                options = axis.options;
                if (axis instanceof CategoryAxis && options.select && !options.vertical) {
                    min = 0;
                    max = options.categories.length - 1;

                    if (axis instanceof DateCategoryAxis) {
                        min = options.categories[min];
                        max = options.categories[max];
                    }

                    if (!options.justified) {
                        if (axis instanceof DateCategoryAxis) {
                            max = addDuration(max, 1, options.baseUnit, options.weekStartDay);
                        } else {
                            max++;
                        }
                    }

                    selection = new Selection(chart, axis,
                        deepExtend({ min: min, max: max }, options.select)
                    );

                    selection.bind(SELECT_START, chart._selectStartHandler);
                    selection.bind(SELECT, chart._selectHandler);
                    selection.bind(SELECT_END, chart._selectEndHandler);

                    selections.push(selection);
                }
            }
        },

        _selectStart: function (e) {
            return this.trigger(SELECT_START, e);
        },

        _select: function (e) {
            return this.trigger(SELECT, e);
        },

        _selectEnd: function (e) {
            return this.trigger(SELECT_END, e);
        },

        _attachEvents: function () {
            var chart = this,
                element = chart.element;

            element.on(CONTEXTMENU_NS, proxy(chart._click, chart));
            element.on(MOUSEOVER_NS, proxy(chart._mouseover, chart));
            element.on(MOUSEOUT_NS, proxy(chart._mouseout, chart));
            element.on(MOUSEWHEEL_NS, proxy(chart._mousewheel, chart));
            element.on(MOUSELEAVE_NS, proxy(chart._mouseleave, chart));

            chart._mousemove = kendo.throttle(
                proxy(chart._mousemove, chart),
                MOUSEMOVE_DELAY
            );

            if (chart._shouldAttachMouseMove()) {
                element.on(MOUSEMOVE_NS, chart._mousemove);
            }

            if (kendo.UserEvents) {
                chart._userEvents = new kendo.UserEvents(element, {
                    global: true,
                    filter: ":not(.k-selector)",
                    multiTouch: false,
                    tap: proxy(chart._tap, chart),
                    start: proxy(chart._start, chart),
                    move: proxy(chart._move, chart),
                    end: proxy(chart._end, chart)
                });
            }
        },

        _mouseout: function (e) {
            var chart = this,
                element = chart._model.modelMap[e.target.getAttribute("data-model-id")];

            if (element && element.leave) {
                element.leave(chart, e);
            }
        },

        _start: function (e) {
            var chart = this,
                events = chart._events;

            if (defined(events[DRAG_START] || events[DRAG] || events[DRAG_END])) {
                chart._startNavigation(e, DRAG_START);
            }
        },

        _move: function (e) {
            var chart = this,
                state = chart._navState,
                axes,
                ranges = {},
                i, currentAxis, axisName, axis, delta;

            if (state) {
                e.preventDefault();

                axes = state.axes;

                for (i = 0; i < axes.length; i++) {
                    currentAxis = axes[i];
                    axisName = currentAxis.options.name;
                    if (axisName) {
                        axis = currentAxis.options.vertical ? e.y : e.x;
                        delta = axis.startLocation - axis.location;

                        if (delta !== 0) {
                            ranges[currentAxis.options.name] =
                                currentAxis.translateRange(delta);
                        }
                    }
                }

                state.axisRanges = ranges;
                chart.trigger(DRAG, {
                    axisRanges: ranges,
                    originalEvent: e
                });
            }
        },

        _end: function (e) {
            this._endNavigation(e, DRAG_END);
        },

        _mousewheel: function (e) {
            var chart = this,
                origEvent = e.originalEvent,
                prevented,
                delta = mwDelta(e),
                totalDelta,
                state = chart._navState,
                axes,
                i,
                currentAxis,
                axisName,
                ranges = {};

            if (!state) {
                prevented = chart._startNavigation(origEvent, ZOOM_START);
                if (!prevented) {
                    state = chart._navState;
                }
            }

            if (state) {
                totalDelta = state.totalDelta || delta;
                state.totalDelta = totalDelta + delta;

                axes = chart._navState.axes;

                for (i = 0; i < axes.length; i++) {
                    currentAxis = axes[i];
                    axisName = currentAxis.options.name;
                    if (axisName) {
                        ranges[axisName] = currentAxis.scaleRange(-totalDelta);
                    }
                }

                chart.trigger(ZOOM, {
                    delta: delta,
                    axisRanges: ranges,
                    originalEvent: e
                });

                if (chart._mwTimeout) {
                    clearTimeout(chart._mwTimeout);
                }

                chart._mwTimeout = setTimeout(function () {
                    chart._endNavigation(e, ZOOM_END);
                }, MOUSEWHEEL_DELAY);
            }
        },

        _startNavigation: function (e, chartEvent) {
            var chart = this,
                coords = chart._eventCoordinates(e),
                plotArea = chart._model._plotArea,
                pane = plotArea.findPointPane(coords),
                axes = plotArea.axes.slice(0),
                i,
                currentAxis,
                inAxis = false,
                prevented;

            if (!pane) {
                return;
            }

            for (i = 0; i < axes.length; i++) {
                currentAxis = axes[i];
                if (currentAxis.box.containsPoint(coords)) {
                    inAxis = true;
                    break;
                }
            }

            if (!inAxis && plotArea.backgroundBox().containsPoint(coords)) {
                prevented = chart.trigger(chartEvent, {
                    axisRanges: axisRanges(axes),
                    originalEvent: e
                });

                if (prevented) {
                    chart._userEvents.cancel();
                } else {
                    chart._suppressHover = true;
                    chart._unsetActivePoint();
                    chart._navState = {
                        pane: pane,
                        axes: axes
                    };
                }
            }
        },

        _endNavigation: function (e, chartEvent) {
            var chart = this;

            if (chart._navState) {
                chart.trigger(chartEvent, {
                    axisRanges: chart._navState.axisRanges,
                    originalEvent: e
                });
                chart._suppressHover = false;
                chart._navState = null;
            }
        },

        _getChartElement: function (e, match) {
            var element = this.surface.eventTarget(e);
            if (!element) {
                return;
            }

            var chartElement;
            while (element && !chartElement) {
                chartElement = element.chartElement;
                element = element.parent;
            }

            if (chartElement) {
                if (chartElement.aliasFor) {
                    chartElement = chartElement.aliasFor(e, this._eventCoordinates(e));
                }

                if (match) {
                    chartElement = chartElement.closest(match);
                }

                return chartElement;
            }
        },

        _eventCoordinates: function (e) {
            var chart = this,
                isTouch = defined((e.x || {}).client),
                clientX = isTouch ? e.x.client : e.clientX,
                clientY = isTouch ? e.y.client : e.clientY;

            return chart._toModelCoordinates(clientX, clientY);
        },

        _toModelCoordinates: function (clientX, clientY) {
            var element = this.element,
                offset = element.offset(),
                paddingLeft = parseInt(element.css("paddingLeft"), 10),
                paddingTop = parseInt(element.css("paddingTop"), 10),
                win = $(window);

            return new Point2D(
                clientX - offset.left - paddingLeft + win.scrollLeft(),
                clientY - offset.top - paddingTop + win.scrollTop()
            );
        },

        _tap: function (e) {
            var chart = this,
                element = chart._getChartElement(e);

            if (chart._activePoint === element) {
                chart._click(e);
            } else {
                if (!chart._startHover(e)) {
                    chart._unsetActivePoint();
                }

                chart._click(e);
            }
        },

        _click: function (e) {
            var chart = this,
                element = chart._getChartElement(e);

            while (element) {
                if (element.click) {
                    element.click(chart, e);
                }

                element = element.parent;
            }
        },

        _startHover: function (e) {
            var chart = this,
                element = chart._getChartElement(e),
                tooltip = chart._tooltip,
                highlight = chart._highlight,
                tooltipOptions = chart.options.tooltip,
                point;

            if (chart._suppressHover || !highlight || highlight.isHighlighted(element) || chart._sharedTooltip()) {
                return;
            }

            point = chart._getChartElement(e, function (element) {
                return element.hover;
            });

            if (point && !point.hover(chart, e)) {
                chart._activePoint = point;

                tooltipOptions = deepExtend({}, tooltipOptions, point.options.tooltip);
                if (tooltipOptions.visible) {
                    tooltip.show(point);
                }

                highlight.show(point);

                return true;
            }
        },

        _mouseover: function (e) {
            var chart = this;

            if (chart._startHover(e)) {
                $(document).on(MOUSEMOVE_TRACKING, proxy(chart._mouseMoveTracking, chart));
            }
        },

        _mouseMoveTracking: function (e) {
            var chart = this,
                options = chart.options,
                tooltip = chart._tooltip,
                highlight = chart._highlight,
                coords = chart._eventCoordinates(e),
                point = chart._activePoint,
                tooltipOptions, owner, seriesPoint;

            if (chart._plotArea.box.containsPoint(coords)) {
                if (point && point.tooltipTracking && point.series && point.parent.getNearestPoint) {
                    seriesPoint = point.parent.getNearestPoint(coords.x, coords.y, point.seriesIx);
                    if (seriesPoint && seriesPoint != point) {
                        seriesPoint.hover(chart, e);
                        chart._activePoint = seriesPoint;

                        tooltipOptions = deepExtend({}, options.tooltip, point.options.tooltip);
                        if (tooltipOptions.visible) {
                            tooltip.show(seriesPoint);
                        }

                        highlight.show(seriesPoint);
                    }
                }
            } else {
                $(document).off(MOUSEMOVE_TRACKING);
                chart._unsetActivePoint();
            }
        },

        _mousemove: function (e) {
            var coords = this._eventCoordinates(e);

            this._trackCrosshairs(coords);

            if (this._sharedTooltip()) {
                this._trackSharedTooltip(coords);
            }
        },

        _trackCrosshairs: function (coords) {
            var crosshairs = this._plotArea.crosshairs,
                i,
                current;

            for (i = 0; i < crosshairs.length; i++) {
                current = crosshairs[i];

                if (current.box.containsPoint(coords)) {
                    current.showAt(coords);
                } else {
                    current.hide();
                }
            }
        },

        _trackSharedTooltip: function (coords) {
            var chart = this,
                options = chart.options,
                plotArea = chart._plotArea,
                categoryAxis = plotArea.categoryAxis,
                tooltip = chart._tooltip,
                tooltipOptions = options.tooltip,
                highlight = chart._highlight,
                index, points;

            if (plotArea.box.containsPoint(coords)) {
                index = categoryAxis.pointCategoryIndex(coords);
                if (index !== chart._tooltipCategoryIx) {
                    points = plotArea.pointsByCategoryIndex(index);

                    if (points.length > 0) {
                        if (tooltipOptions.visible) {
                            tooltip.showAt(points, coords);
                        }

                        highlight.show(points);
                    } else {
                        tooltip.hide();
                    }

                    chart._tooltipCategoryIx = index;
                }
            }
        },

        _mouseleave: function (e) {
            var chart = this,
                plotArea = chart._plotArea,
                crosshairs = plotArea.crosshairs,
                tooltip = chart._tooltip,
                highlight = chart._highlight,
                target = e.relatedTarget,
                i;

            if (target) {
                for (i = 0; i < crosshairs.length; i++) {
                    crosshairs[i].hide();
                }

                highlight.hide();

                var tooltipElement = tooltip.element[0];
                if (target !== tooltipElement && !$.contains(tooltipElement, target)) {
                    setTimeout(proxy(tooltip.hide, tooltip), TOOLTIP_HIDE_DELAY);
                    chart._tooltipCategoryIx = null;
                }
            }
        },

        _unsetActivePoint: function () {
            var chart = this,
                tooltip = chart._tooltip,
                highlight = chart._highlight;

            chart._activePoint = null;

            if (tooltip) {
                tooltip.hide();
            }

            if (highlight) {
                highlight.hide();
            }
        },

        _onDataChanged: function () {
            var chart = this,
                options = chart.options,
                series = chart._sourceSeries || options.series,
                seriesIx,
                seriesLength = series.length,
                data = chart.dataSource.view(),
                grouped = (chart.dataSource.group() || []).length > 0,
                processedSeries = [],
                currentSeries;

            for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
                currentSeries = series[seriesIx];

                if (chart._isBindable(currentSeries) && grouped) {
                    append(processedSeries,
                           groupSeries(currentSeries, data));
                } else {
                    processedSeries.push(currentSeries || []);
                }
            }

            chart._sourceSeries = series;
            options.series = processedSeries;

            applySeriesColors(chart.options);

            chart._bindSeries();
            chart._bindCategories();
            chart._hasData = true;

            chart._deferRedraw();
        },

        _deferRedraw: function () {
            var chart = this;

            if (kendo.support.vml) {
                chart._clearRedrawTimeout();
                chart._redrawTimeout = setTimeout(function () {
                    if (!chart.surface) {
                        return;
                    }

                    chart.trigger(DATABOUND);
                    chart._redraw();
                }, 0);
            } else {
                chart.trigger(DATABOUND);
                chart._redraw();
            }
        },

        _clearRedrawTimeout: function () {
            if (this._redrawTimeout) {
                clearInterval(this._redrawTimeout);
                this._redrawTimeout = null;
            }
        },

        _bindSeries: function () {
            var chart = this,
                data = chart.dataSource.view(),
                series = chart.options.series,
                seriesIx,
                seriesLength = series.length,
                currentSeries,
                groupIx,
                seriesData;

            for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
                currentSeries = series[seriesIx];

                if (chart._isBindable(currentSeries)) {
                    groupIx = currentSeries._groupIx;
                    seriesData = defined(groupIx) ? (data[groupIx] || {}).items : data;

                    if (currentSeries.autoBind !== false) {
                        currentSeries.data = seriesData;
                    }
                }
            }
        },

        _bindCategories: function () {
            var chart = this,
                data = chart.dataSource.view() || [],
                grouped = (chart.dataSource.group() || []).length > 0,
                categoriesData = data,
                options = chart.options,
                definitions = [].concat(options.categoryAxis),
                axisIx,
                axis;

            if (grouped) {
                if (data.length) {
                    categoriesData = data[0].items;
                }
            }

            for (axisIx = 0; axisIx < definitions.length; axisIx++) {
                axis = definitions[axisIx];
                if (axis.autoBind !== false) {
                    chart._bindCategoryAxis(axis, categoriesData, axisIx);
                }
            }
        },

        _bindCategoryAxis: function (axis, data, axisIx) {
            var count = (data || []).length,
                categoryIx,
                category,
                row;

            if (axis.field) {
                axis.categories = [];
                for (categoryIx = 0; categoryIx < count; categoryIx++) {
                    row = data[categoryIx];

                    category = getField(axis.field, row);
                    if (categoryIx === 0) {
                        axis.categories = [category];
                        axis.dataItems = [row];
                    } else {
                        axis.categories.push(category);
                        axis.dataItems.push(row);
                    }
                }
            } else {
                this._bindCategoryAxisFromSeries(axis, axisIx);
            }
        },

        _bindCategoryAxisFromSeries: function (axis, axisIx) {
            var chart = this,
                items = [],
                result,
                series = chart.options.series,
                seriesLength = series.length,
                seriesIx,
                s,
                onAxis,
                data,
                dataIx,
                dataLength,
                dataRow,
                category,
                uniqueCategories = {},
                getFn,
                dateAxis;

            for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
                s = series[seriesIx];
                onAxis = s.categoryAxis === axis.name || (!s.categoryAxis && axisIx === 0);
                data = s.data;
                dataLength = data.length;

                if (s.categoryField && onAxis && dataLength > 0) {
                    dateAxis = isDateAxis(axis, getField(s.categoryField, data[0]));
                    getFn = dateAxis ? getDateField : getField;

                    for (dataIx = 0; dataIx < dataLength; dataIx++) {
                        dataRow = data[dataIx];
                        category = getFn(s.categoryField, dataRow);

                        if (dateAxis || !uniqueCategories[category]) {
                            items.push([category, dataRow]);

                            if (!dateAxis) {
                                uniqueCategories[category] = true;
                            }
                        }
                    }
                }
            }

            if (items.length > 0) {
                if (dateAxis) {
                    items = uniqueDates(items, function (a, b) {
                        return dateComparer(a[0], b[0]);
                    });
                }

                result = transpose(items);
                axis.categories = result[0];
                axis.dataItems = result[1];
            }
        },

        _isBindable: function (series) {
            var valueFields = SeriesBinder.current.valueFields(series),
                result = true,
                field, i;

            for (i = 0; i < valueFields.length; i++) {
                field = valueFields[i];
                if (field === VALUE) {
                    field = "field";
                } else {
                    field = field + "Field";
                }

                if (!defined(series[field])) {
                    result = false;
                    break;
                }
            }

            return result;
        },

        _legendItemClick: function (seriesIndex, pointIndex) {
            var chart = this,
                plotArea = chart._plotArea,
                currentSeries = (plotArea.srcSeries || plotArea.series)[seriesIndex],
                originalSeries = (chart._sourceSeries || [])[seriesIndex] || currentSeries,
                transitionsState, visible, point;

            if (inArray(currentSeries.type, [PIE, DONUT, FUNNEL])) {
                point = originalSeries.data[pointIndex];
                if (!defined(point.visible)) {
                    visible = false;
                } else {
                    visible = !point.visible;
                }
                point.visible = visible;
            } else {
                visible = !originalSeries.visible;
                originalSeries.visible = visible;
                currentSeries.visible = visible;
            }

            if (chart.options.transitions) {
                chart.options.transitions = false;
                transitionsState = true;
            }
            chart.redraw();
            if (transitionsState) {
                chart.options.transitions = true;
            }
        },

        _legendItemHover: function (seriesIndex, pointIndex) {
            var chart = this,
                plotArea = chart._plotArea,
                highlight = chart._highlight,
                currentSeries = (plotArea.srcSeries || plotArea.series)[seriesIndex],
                index, items;

            if (inArray(currentSeries.type, [PIE, DONUT, FUNNEL])) {
                index = pointIndex;
            } else {
                index = seriesIndex;
            }

            items = plotArea.pointsBySeriesIndex(index);
            highlight.show(items);
        },

        _shouldAttachMouseMove: function () {
            var chart = this;

            return chart._plotArea.crosshairs.length || (chart._tooltip && chart._sharedTooltip());
        },

        setOptions: function (options) {
            var chart = this,
                dataSource = options.dataSource;

            options.dataSource = undefined;

            chart._originalOptions = deepExtend(chart._originalOptions, options);
            chart.options = deepExtend({}, chart._originalOptions);
            chart._sourceSeries = null;
            $(document).off(MOUSEMOVE_NS);

            Widget.fn._setEvents.call(chart, options);

            chart._initTheme(chart.options);

            if (dataSource) {
                chart.setDataSource(dataSource);
            }

            if (chart._shouldAttachMouseMove()) {
                chart.element.on(MOUSEMOVE_NS, chart._mousemove);
            }

            if (chart._hasDataSource) {
                chart.refresh();
            } else {
                chart.redraw();
            }
        },

        destroy: function () {
            var chart = this,
                dataSource = chart.dataSource;

            chart.element.off(NS);
            dataSource.unbind(CHANGE, chart._dataChangeHandler);
            $(document).off(MOUSEMOVE_TRACKING);

            if (chart._userEvents) {
                chart._userEvents.destroy();
            }

            chart._destroyView();

            chart.surface.destroy();
            chart.surface = null;

            chart._clearRedrawTimeout();

            Widget.fn.destroy.call(chart);
        },

        _destroyView: function () {
            var chart = this,
                model = chart._model,
                selections = chart._selections;

            if (model) {
                model.destroy();
                chart._model = null;
            }

            if (selections) {
                while (selections.length > 0) {
                    selections.shift().destroy();
                }
            }

            chart._unsetActivePoint();

            if (chart._tooltip) {
                chart._tooltip.destroy();
            }

            if (chart._highlight) {
                chart._highlight.destroy();
            }
        }
    });

    dataviz.ExportMixin.extend(Chart.fn);

    if (kendo.PDFMixin) {
        kendo.PDFMixin.extend(Chart.fn);
    }

    var PlotAreaFactory = Class.extend({
        init: function () {
            this._registry = [];
        },

        register: function (type, seriesTypes) {
            this._registry.push({
                type: type,
                seriesTypes: seriesTypes
            });
        },

        create: function (srcSeries, options) {
            var registry = this._registry,
                match = registry[0],
                i,
                entry,
                series;

            for (i = 0; i < registry.length; i++) {
                entry = registry[i];
                series = filterSeriesByType(srcSeries, entry.seriesTypes);

                if (series.length > 0) {
                    match = entry;
                    break;
                }
            }

            return new match.type(series, options);
        }
    });
    PlotAreaFactory.current = new PlotAreaFactory();

    var SeriesBinder = Class.extend({
        init: function () {
            this._valueFields = {};
            this._otherFields = {};
            this._nullValue = {};
            this._undefinedValue = {};
        },

        register: function (seriesTypes, valueFields, otherFields) {
            var binder = this,
                i,
                type;

            valueFields = valueFields || [VALUE];

            for (i = 0; i < seriesTypes.length; i++) {
                type = seriesTypes[i];

                binder._valueFields[type] = valueFields;
                binder._otherFields[type] = otherFields;
                binder._nullValue[type] = binder._makeValue(valueFields, null);
                binder._undefinedValue[type] = binder._makeValue(valueFields, undefined);
            }
        },

        canonicalFields: function (series) {
            return this.valueFields(series).concat(this.otherFields(series));
        },

        valueFields: function (series) {
            return this._valueFields[series.type] || [VALUE];
        },

        otherFields: function (series) {
            return this._otherFields[series.type] || [VALUE];
        },

        bindPoint: function (series, pointIx) {
            var binder = this,
                data = series.data,
                pointData = data[pointIx],
                result = { valueFields: { value: pointData} },
                fields, fieldData,
                srcValueFields, srcPointFields,
                valueFields = binder.valueFields(series),
                otherFields = binder._otherFields[series.type],
                value;

            if (pointData === null) {
                value = binder._nullValue[series.type];
            } else if (!defined(pointData)) {
                value = binder._undefinedValue[series.type];
            } else if (isArray(pointData)) {
                fieldData = pointData.slice(valueFields.length);
                value = binder._bindFromArray(pointData, valueFields);
                fields = binder._bindFromArray(fieldData, otherFields);
            } else if (typeof pointData === OBJECT) {
                srcValueFields = binder.sourceFields(series, valueFields);
                srcPointFields = binder.sourceFields(series, otherFields);

                value = binder._bindFromObject(pointData, valueFields, srcValueFields);
                fields = binder._bindFromObject(pointData, otherFields, srcPointFields);
            }

            if (defined(value)) {
                if (valueFields.length === 1) {
                    result.valueFields.value = value[valueFields[0]];
                } else {
                    result.valueFields = value;
                }
            }

            result.fields = fields || {};

            return result;
        },

        _makeValue: function (fields, initialValue) {
            var value = {},
                i,
                length = fields.length,
                fieldName;

            for (i = 0; i < length; i++) {
                fieldName = fields[i];
                value[fieldName] = initialValue;
            }

            return value;
        },

        _bindFromArray: function (array, fields) {
            var value = {},
                i,
                length;

            if (fields) {
                length = math.min(fields.length, array.length);

                for (i = 0; i < length; i++) {
                    value[fields[i]] = array[i];
                }
            }

            return value;
        },

        _bindFromObject: function (object, fields, srcFields) {
            var value = {},
                i,
                length,
                fieldName,
                srcFieldName;

            if (fields) {
                length = fields.length;
                srcFields = srcFields || fields;

                for (i = 0; i < length; i++) {
                    fieldName = fields[i];
                    srcFieldName = srcFields[i];
                    value[fieldName] = getField(srcFieldName, object);
                }
            }

            return value;
        },

        sourceFields: function (series, canonicalFields) {
            var i, length, fieldName,
                sourceFields, sourceFieldName;

            if (canonicalFields) {
                length = canonicalFields.length;
                sourceFields = [];

                for (i = 0; i < length; i++) {
                    fieldName = canonicalFields[i];
                    sourceFieldName = fieldName === VALUE ? "field" : fieldName + "Field";

                    sourceFields.push(series[sourceFieldName] || fieldName);
                }
            }

            return sourceFields;
        }
    });
    SeriesBinder.current = new SeriesBinder();

    var BarLabel = ChartElement.extend({
        init: function (content, options) {
            var barLabel = this;
            ChartElement.fn.init.call(barLabel, options);

            this.textBox = new TextBox(content, barLabel.options);
            barLabel.append(this.textBox);
        },

        options: {
            position: OUTSIDE_END,
            margin: getSpacing(3),
            padding: getSpacing(4),
            color: BLACK,
            background: "",
            border: {
                width: 1,
                color: ""
            },
            aboveAxis: true,
            vertical: false,
            animation: {
                type: FADEIN,
                delay: INITIAL_ANIMATION_DURATION
            },
            zIndex: 2
        },

        createVisual: function () {
            this.textBox.options.noclip = this.options.noclip;
        },

        reflow: function (targetBox) {
            var barLabel = this,
                options = barLabel.options,
                vertical = options.vertical,
                aboveAxis = options.aboveAxis,
                text = barLabel.children[0],
                box = text.box,
                padding = text.options.padding;

            text.options.align = vertical ? CENTER : LEFT;
            text.options.vAlign = vertical ? TOP : CENTER;

            if (options.position == INSIDE_END) {
                if (vertical) {
                    text.options.vAlign = TOP;

                    if (!aboveAxis && box.height() < targetBox.height()) {
                        text.options.vAlign = BOTTOM;
                    }
                } else {
                    text.options.align = aboveAxis ? RIGHT : LEFT;
                }
            } else if (options.position == CENTER) {
                text.options.vAlign = CENTER;
                text.options.align = CENTER;
            } else if (options.position == INSIDE_BASE) {
                if (vertical) {
                    text.options.vAlign = aboveAxis ? BOTTOM : TOP;
                } else {
                    text.options.align = aboveAxis ? LEFT : RIGHT;
                }
            } else if (options.position == OUTSIDE_END) {
                if (vertical) {
                    if (aboveAxis) {
                        targetBox = new Box2D(
                            targetBox.x1, targetBox.y1 - box.height(),
                            targetBox.x2, targetBox.y1
                        );
                    } else {
                        targetBox = new Box2D(
                            targetBox.x1, targetBox.y2,
                            targetBox.x2, targetBox.y2 + box.height()
                        );
                    }
                } else {
                    text.options.align = CENTER;
                    if (aboveAxis) {
                        targetBox = new Box2D(
                            targetBox.x2 + box.width(), targetBox.y1,
                            targetBox.x2, targetBox.y2
                        );
                    } else {
                        targetBox = new Box2D(
                            targetBox.x1 - box.width(), targetBox.y1,
                            targetBox.x1, targetBox.y2
                        );
                    }
                }
            }

            if (!options.rotation) {
                if (vertical) {
                    padding.left = padding.right =
                        (targetBox.width() - text.contentBox.width()) / 2;
                } else {
                    padding.top = padding.bottom =
                        (targetBox.height() - text.contentBox.height()) / 2;
                }
            }

            text.reflow(targetBox);
        },

        alignToClipBox: function (clipBox) {
            var barLabel = this,
                vertical = barLabel.options.vertical,
                field = vertical ? Y : X,
                start = field + "1",
                end = field + "2",
                text = barLabel.children[0],
                parentBox = barLabel.parent.box,
                targetBox;

            if (parentBox[start] < clipBox[start] || clipBox[end] < parentBox[end]) {
                targetBox = text.paddingBox.clone();
                targetBox[start] = math.max(parentBox[start], clipBox[start]);
                targetBox[end] = math.min(parentBox[end], clipBox[end]);

                this.reflow(targetBox);
            }
        }
    });

    var LegendItem = BoxElement.extend({
        init: function (options) {
            var item = this;

            BoxElement.fn.init.call(item, options);

            item.createContainer();
            item.createMarker();
            item.createLabel();
        },

        createContainer: function () {
            var item = this;

            item.container = new FloatElement({ vertical: false, wrap: false, align: CENTER });
            item.append(item.container);
        },

        createMarker: function () {
            var item = this,
                options = item.options,
                markerColor = options.markerColor,
                markers = options.markers,
                markerOptions = deepExtend({}, markers, {
                    background: markerColor,
                    border: {
                        color: markerColor
                    }
                });

            item.container.append(new ShapeElement(markerOptions));
        },

        createLabel: function () {
            var item = this,
                options = item.options,
                labelOptions = deepExtend({}, options.labels);

            item.container.append(new TextBox(options.text, labelOptions));
        },

        renderComplete: function () {
            ChartElement.fn.renderComplete.call(this);

            var cursor = this.options.cursor || {};
            var eventSink = draw.Path.fromRect(this.container.box.toRect(), {
                fill: {
                    color: WHITE,
                    opacity: 0
                },
                stroke: null,
                cursor: cursor.style
            });

            this.visual.append(eventSink);
        },

        click: function (widget, e) {
            var args = this.eventArgs(e);

            if (!widget.trigger(LEGEND_ITEM_CLICK, args)) {
                e.preventDefault();
                widget._legendItemClick(args.seriesIndex, args.pointIndex);
            }
        },

        hover: function (widget, e) {
            var args = this.eventArgs(e);

            if (!widget.trigger(LEGEND_ITEM_HOVER, args)) {
                e.preventDefault();
                widget._legendItemHover(args.seriesIndex, args.pointIndex);
            }

            // Don't trigger point hover for legend items
            return true;
        },

        leave: function (widget) {
            widget._unsetActivePoint();
        },

        eventArgs: function (e) {
            var options = this.options;

            return {
                element: $(e.target),
                text: options.text,
                series: options.series,
                seriesIndex: options.series.index,
                pointIndex: options.pointIndex
            };
        }
    });

    var Legend = ChartElement.extend({
        init: function (options) {
            var legend = this;

            ChartElement.fn.init.call(legend, options);

            if (!inArray(legend.options.position, [TOP, RIGHT, BOTTOM, LEFT, CUSTOM])) {
                legend.options.position = RIGHT;
            }

            legend.createContainer();

            legend.createItems();
        },

        options: {
            position: RIGHT,
            items: [],
            labels: {
                margin: {
                    left: 6
                }
            },
            offsetX: 0,
            offsetY: 0,
            margin: getSpacing(5),
            padding: getSpacing(5),
            border: {
                color: BLACK,
                width: 0
            },
            item: {
                zIndex: 1,
                cursor: {
                    style: POINTER
                }
            },
            spacing: 6,
            background: "",
            zIndex: 1,
            markers: {
                border: {
                    width: 1
                },
                width: 7,
                height: 7,
                type: "rect",
                align: LEFT,
                vAlign: CENTER,
                zIndex: 1
            }
        },

        createContainer: function () {
            var legend = this,
                options = legend.options,
                position = options.position,
                align = position,
                vAlign = CENTER;

            if (position == CUSTOM) {
                align = LEFT;
            } else if (inArray(position, [TOP, BOTTOM])) {
                align = CENTER;
                vAlign = position;
            }

            legend.container = new BoxElement({
                margin: options.margin,
                padding: options.padding,
                background: options.background,
                border: options.border,
                vAlign: vAlign,
                align: align,
                zIndex: options.zIndex,
                shrinkToFit: true
            });

            legend.append(legend.container);
        },

        createItems: function () {
            var legend = this,
                options = legend.options,
                items = options.items,
                count = items.length,
                vertical = legend.isVertical(),
                innerElement, i, item;

            innerElement = new FloatElement({
                vertical: vertical,
                spacing: options.spacing
            });

            for (i = 0; i < count; i++) {
                item = items[i];

                innerElement.append(new LegendItem(deepExtend({}, {
                    markers: options.markers,
                    labels: options.labels
                }, options.item, item)));
            }
            legend.container.append(innerElement);
        },

        isVertical: function () {
            var legend = this,
                options = legend.options,
                position = options.position,
                vertical = inArray(position, [LEFT, RIGHT]) ||
                    (position == CUSTOM && options.orientation != HORIZONTAL);

            return vertical;
        },

        hasItems: function () {
            return this.container.children[0].children.length > 0;
        },

        reflow: function (targetBox) {
            var legend = this,
                options = legend.options,
                container = legend.container,
                vertical = legend.isVertical(),
                containerBox = targetBox.clone();

            if (!legend.hasItems()) {
                legend.box = targetBox.clone();
                return;
            }

            if (vertical) {
                containerBox.y1 = 0;
            }

            if (options.position === CUSTOM) {
                legend.containerCustomReflow(containerBox);
                legend.box = targetBox.clone();
            } else {
                container.reflow(containerBox);
                legend.containerReflow(targetBox);
            }
        },

        containerReflow: function (targetBox) {
            var legend = this,
                options = legend.options,
                pos = options.position == TOP || options.position == BOTTOM ? X : Y,
                containerBox = legend.container.box,
                box = containerBox.clone();

            if (options.offsetX || options.offsetY) {
                containerBox.translate(options.offsetX, options.offsetY);
                legend.container.reflow(containerBox);
            }

            box[pos + 1] = targetBox[pos + 1];
            box[pos + 2] = targetBox[pos + 2];

            legend.box = box;
        },

        containerCustomReflow: function (targetBox) {
            var legend = this,
                options = legend.options,
                offsetX = options.offsetX,
                offsetY = options.offsetY,
                container = legend.container,
                width = options.width,
                height = options.height,
                vertical = legend.isVertical(),
                containerBox = targetBox.clone();

            if (vertical && height) {
                containerBox.y2 = containerBox.y1 + height;
            } else if (!vertical && width) {
                containerBox.x2 = containerBox.x1 + width;
            }
            container.reflow(containerBox);
            containerBox = container.box;

            container.reflow(Box2D(
                offsetX, offsetY,
                offsetX + containerBox.width(), offsetY + containerBox.height()
            ));
        }
    });

    var CategoryAxis = Axis.extend({
        init: function (options) {
            var axis = this;

            Axis.fn.init.call(axis, options);

            options = axis.options;
            options.categories = options.categories.slice(0);

            axis._ticks = {};
        },

        options: {
            type: CATEGORY,
            categories: [],
            vertical: false,
            majorGridLines: {
                visible: false,
                width: 1,
                color: BLACK
            },
            labels: {
                zIndex: 1
            },
            justified: false
        },

        range: function () {
            return { min: 0, max: this.options.categories.length };
        },

        getTickPositions: function (itemsCount) {
            var axis = this,
                options = axis.options,
                vertical = options.vertical,
                justified = options.justified,
                lineBox = axis.lineBox(),
                size = vertical ? lineBox.height() : lineBox.width(),
                intervals = itemsCount - (justified ? 1 : 0),
                step = size / intervals,
                dim = vertical ? Y : X,
                pos = lineBox[dim + 1],
                positions = [],
                i;

            for (i = 0; i < itemsCount; i++) {
                positions.push(round(pos, COORD_PRECISION));
                pos += step;
            }

            if (!justified) {
                positions.push(lineBox[dim + 2]);
            }

            return options.reverse ? positions.reverse() : positions;
        },

        getMajorTickPositions: function () {
            return this.getTicks().majorTicks;
        },

        getMinorTickPositions: function () {
            return this.getTicks().minorTicks;
        },

        getTicks: function () {
            var axis = this,
                cache = axis._ticks,
                options = axis.options,
                count = options.categories.length,
                reverse = options.reverse,
                justified = options.justified,
                lineBox = axis.lineBox(),
                hash;

            hash = lineBox.getHash() + count + reverse + justified;
            if (cache._hash !== hash) {
                cache._hash = hash;
                cache.majorTicks = axis.getTickPositions(count);
                cache.minorTicks = axis.getTickPositions(count * 2);
            }

            return cache;
        },

        getSlot: function (from, to) {
            var axis = this,
                options = axis.options,
                majorTicks = axis.getTicks().majorTicks,
                reverse = options.reverse,
                justified = options.justified,
                valueAxis = options.vertical ? Y : X,
                lineBox = axis.lineBox(),
                lineStart = lineBox[valueAxis + (reverse ? 2 : 1)],
                lineEnd = lineBox[valueAxis + (reverse ? 1 : 2)],
                slotBox = lineBox.clone(),
                intervals = math.max(1, majorTicks.length - (justified ? 0 : 1)),
                p1,
                p2,
                slotSize;

            var singleSlot = !defined(to);

            from = valueOrDefault(from, 0);
            to = valueOrDefault(to, from);
            from = limitValue(from, 0, intervals);
            to = limitValue(to - 1, from, intervals);
            // Fixes transient bug caused by iOS 6.0 JIT
            // (one can never be too sure)
            to = math.max(from, to);

            p1 = from === 0 ? lineStart : (majorTicks[from] || lineEnd);
            p2 = justified ? p1 : majorTicks[to];
            slotSize = to - from;

            if (slotSize > 0 || (from === to)) {
                p2 = majorTicks[to + 1] || lineEnd;
            }

            if (singleSlot && justified) {
                if (from === intervals) {
                    p1 = p2;
                } else {
                    p2 = p1;
                }
            }

            slotBox[valueAxis + 1] = reverse ? p2 : p1;
            slotBox[valueAxis + 2] = reverse ? p1 : p2;

            return slotBox;
        },

        pointCategoryIndex: function (point) {
            var axis = this,
                options = axis.options,
                reverse = options.reverse,
                vertical = options.vertical,
                valueAxis = vertical ? Y : X,
                lineBox = axis.lineBox(),
                lineStart = lineBox[valueAxis + 1],
                lineEnd = lineBox[valueAxis + 2],
                pos = point[valueAxis],
                majorTicks = axis.getMajorTickPositions(),
                diff = MAX_VALUE,
                tickPos, nextTickPos, i, categoryIx;

            if (pos < lineStart || pos > lineEnd) {
                return null;
            }

            for (i = 0; i < majorTicks.length; i++) {
                tickPos = majorTicks[i];
                nextTickPos = majorTicks[i + 1];

                if (!defined(nextTickPos)) {
                    nextTickPos = reverse ? lineStart : lineEnd;
                }

                if (reverse) {
                    tickPos = nextTickPos;
                    nextTickPos = majorTicks[i];
                }

                if (options.justified) {
                    if (pos === nextTickPos) {
                        categoryIx = math.max(0, vertical ? majorTicks.length - i - 1 : i + 1);
                        break;
                    }

                    if (math.abs(pos - tickPos) < diff) {
                        diff = pos - tickPos;
                        categoryIx = i;
                    }
                } else {
                    if (pos >= tickPos && pos <= nextTickPos) {
                        categoryIx = i;
                        break;
                    }
                }
            }

            return categoryIx;
        },

        getCategory: function (point) {
            var index = this.pointCategoryIndex(point);

            if (index === null) {
                return null;
            }
            return this.options.categories[index];
        },

        categoryIndex: function (value) {
            return indexOf(value, this.options.categories);
        },

        translateRange: function (delta) {
            var axis = this,
                options = axis.options,
                lineBox = axis.lineBox(),
                size = options.vertical ? lineBox.height() : lineBox.width(),
                range = options.categories.length,
                scale = size / range,
                offset = round(delta / scale, DEFAULT_PRECISION);

            return {
                min: offset,
                max: range + offset
            };
        },

        scaleRange: function (scale) {
            var axis = this,
                options = axis.options,
                range = options.categories.length,
                delta = scale * range;

            return {
                min: -delta,
                max: range + delta
            };
        },

        labelsCount: function () {
            return this.options.categories.length;
        },

        createAxisLabel: function (index, labelOptions) {
            var axis = this,
                options = axis.options,
                dataItem = options.dataItems ? options.dataItems[index] : null,
                category = valueOrDefault(options.categories[index], ""),
                text = axis.axisLabelText(category, dataItem, labelOptions);

            return new AxisLabel(category, text, index, dataItem, labelOptions);
        },

        shouldRenderNote: function (value) {
            var categories = this.options.categories;

            return categories.length && (categories.length > value && value >= 0);
        }
    });

    var DateCategoryAxis = CategoryAxis.extend({
        init: function (options) {
            var axis = this,
                baseUnit,
                useDefault;

            options = options || {};

            options = deepExtend({
                roundToBaseUnit: true
            }, options, {
                categories: toDate(options.categories),
                min: toDate(options.min),
                max: toDate(options.max)
            });

            if (options.categories && options.categories.length > 0) {
                baseUnit = (options.baseUnit || "").toLowerCase();
                useDefault = baseUnit !== FIT && !inArray(baseUnit, BASE_UNITS);
                if (useDefault) {
                    options.baseUnit = axis.defaultBaseUnit(options);
                }

                if (baseUnit === FIT || options.baseUnitStep === AUTO) {
                    axis.autoBaseUnit(options);
                }

                axis.groupCategories(options);
            } else {
                options.baseUnit = options.baseUnit || DAYS;
            }

            CategoryAxis.fn.init.call(axis, options);
        },

        options: {
            type: DATE,
            labels: {
                dateFormats: DateLabelFormats
            },
            autoBaseUnitSteps: {
                seconds: [1, 2, 5, 15, 30],
                minutes: [1, 2, 5, 15, 30],
                hours: [1, 2, 3],
                days: [1, 2, 3],
                weeks: [1, 2],
                months: [1, 2, 3, 6],
                years: [1, 2, 3, 5, 10, 25, 50]
            },
            maxDateGroups: 10
        },

        shouldRenderNote: function (value) {
            var axis = this,
                range = axis.range(),
                categories = axis.options.categories || [];

            return dateComparer(value, range.min) >= 0 && dateComparer(value, range.max) <= 0 && categories.length;
        },

        parseNoteValue: function (value) {
            return toDate(value);
        },

        translateRange: function (delta) {
            var axis = this,
                options = axis.options,
                baseUnit = options.baseUnit,
                weekStartDay = options.weekStartDay,
                lineBox = axis.lineBox(),
                size = options.vertical ? lineBox.height() : lineBox.width(),
                range = axis.range(),
                scale = size / (range.max - range.min),
                offset = round(delta / scale, DEFAULT_PRECISION),
                from,
                to;

            if (range.min && range.max) {
                from = addTicks(options.min || range.min, offset);
                to = addTicks(options.max || range.max, offset);

                range = {
                    min: addDuration(from, 0, baseUnit, weekStartDay),
                    max: addDuration(to, 0, baseUnit, weekStartDay)
                };
            }

            return range;
        },

        scaleRange: function (delta) {
            var axis = this,
                rounds = math.abs(delta),
                range = axis.range(),
                from = range.min,
                to = range.max,
                step;

            if (range.min && range.max) {
                while (rounds--) {
                    range = dateDiff(from, to);
                    step = math.round(range * 0.1);
                    if (delta < 0) {
                        from = addTicks(from, step);
                        to = addTicks(to, -step);
                    } else {
                        from = addTicks(from, -step);
                        to = addTicks(to, step);
                    }
                }

                range = { min: from, max: to };
            }

            return range;
        },

        defaultBaseUnit: function (options) {
            var categories = options.categories,
                count = defined(categories) ? categories.length : 0,
                categoryIx,
                cat,
                diff,
                minDiff = MAX_VALUE,
                lastCat,
                unit;

            for (categoryIx = 0; categoryIx < count; categoryIx++) {
                cat = categories[categoryIx];

                if (cat && lastCat) {
                    diff = dateDiff(cat, lastCat);
                    if (diff > 0) {
                        minDiff = math.min(minDiff, diff);

                        if (minDiff >= TIME_PER_YEAR) {
                            unit = YEARS;
                        } else if (minDiff >= TIME_PER_MONTH - TIME_PER_DAY * 3) {
                            unit = MONTHS;
                        } else if (minDiff >= TIME_PER_WEEK) {
                            unit = WEEKS;
                        } else if (minDiff >= TIME_PER_DAY) {
                            unit = DAYS;
                        } else if (minDiff >= TIME_PER_HOUR) {
                            unit = HOURS;
                        } else if (minDiff >= TIME_PER_MINUTE) {
                            unit = MINUTES;
                        } else {
                            unit = SECONDS;
                        }
                    }
                }

                lastCat = cat;
            }

            return unit || DAYS;
        },

        _categoryRange: function (categories) {
            var range = categories._range;
            if (!range) {
                range = categories._range = sparseArrayLimits(categories);
            }

            return range;
        },

        range: function (options) {
            options = options || this.options;

            var categories = options.categories,
                autoUnit = options.baseUnit === FIT,
                baseUnit = autoUnit ? BASE_UNITS[0] : options.baseUnit,
                baseUnitStep = options.baseUnitStep || 1,
                min = toTime(options.min),
                max = toTime(options.max),
                categoryLimits = this._categoryRange(categories);

            var minCategory = toTime(categoryLimits.min),
                maxCategory = toTime(categoryLimits.max);

            if (options.roundToBaseUnit) {
                return { min: addDuration(min || minCategory, 0, baseUnit, options.weekStartDay),
                    max: addDuration(max || maxCategory, baseUnitStep, baseUnit, options.weekStartDay)
                };
            } else {
                return { min: toDate(min || minCategory),
                    max: toDate(max || this._srcMaxDate || maxCategory)
                };
            }
        },

        autoBaseUnit: function (options) {
            var axis = this,
                range = axis.range(deepExtend({}, options, { baseUnitStep: 1 })),
                autoUnit = options.baseUnit === FIT,
                autoUnitIx = 0,
                baseUnit = autoUnit ? BASE_UNITS[autoUnitIx++] : options.baseUnit,
                span = range.max - range.min,
                units = span / TIME_PER_UNIT[baseUnit],
                totalUnits = units,
                maxDateGroups = options.maxDateGroups || axis.options.maxDateGroups,
                autoBaseUnitSteps = deepExtend(
                    {}, axis.options.autoBaseUnitSteps, options.autoBaseUnitSteps
                ),
                unitSteps,
                step,
                nextStep;

            while (!step || units > maxDateGroups) {
                unitSteps = unitSteps || autoBaseUnitSteps[baseUnit].slice(0);
                nextStep = unitSteps.shift();

                if (nextStep) {
                    step = nextStep;
                    units = totalUnits / step;
                } else if (baseUnit === last(BASE_UNITS)) {
                    step = math.ceil(totalUnits / maxDateGroups);
                    break;
                } else if (autoUnit) {
                    baseUnit = BASE_UNITS[autoUnitIx++] || last(BASE_UNITS);
                    totalUnits = span / TIME_PER_UNIT[baseUnit];
                    unitSteps = null;
                } else {
                    if (units > maxDateGroups) {
                        step = math.ceil(totalUnits / maxDateGroups);
                    }
                    break;
                }
            }

            options.baseUnitStep = step;
            options.baseUnit = baseUnit;
        },

        _timeScale: function () {
            var axis = this,
                range = axis.range(),
                options = axis.options,
                lineBox = axis.lineBox(),
                vertical = options.vertical,
                lineSize = vertical ? lineBox.height() : lineBox.width(),
                timeRange;

            if (options.justified && options._collapse !== false) {
                var categoryLimits = this._categoryRange(options.categories);
                var maxCategory = toTime(categoryLimits.max);
                timeRange = toDate(maxCategory) - range.min;
            } else {
                timeRange = range.max - range.min;
            }

            return lineSize / timeRange;
        },

        getTickPositions: function (count) {
            var axis = this,
                options = axis.options,
                categories = options.categories,
                positions = [];

            if (options.roundToBaseUnit || categories.length === 0) {
                positions = CategoryAxis.fn.getTickPositions.call(axis, count);
            } else {
                var vertical = options.vertical,
                    reverse = options.reverse,
                    lineBox = axis.lineBox(),
                    startTime = categories[0].getTime(),
                    collapse = valueOrDefault(options._collapse, options.justified),
                    divisions = categories.length - (collapse ? 1 : 0),
                    scale = axis._timeScale(),
                    dir = (vertical ? -1 : 1) * (reverse ? -1 : 1),
                    startEdge = dir === 1 ? 1 : 2,
                    endEdge = dir === 1 ? 2 : 1,
                    startPos = lineBox[(vertical ? Y : X) + startEdge],
                    endPos = lineBox[(vertical ? Y : X) + endEdge],
                    pos = startPos,
                    i,
                    timePos;

                for (i = 0; i < divisions; i++) {
                    timePos = categories[i] - startTime;
                    pos = startPos + timePos * scale * dir;
                    positions.push(round(pos, COORD_PRECISION));
                }

                if (last(positions) !== endPos) {
                    positions.push(endPos);
                }
            }

            return positions;
        },

        groupCategories: function (options) {
            var axis = this,
                categories = options.categories,
                maxCategory = toDate(sparseArrayMax(categories)),
                baseUnit = options.baseUnit,
                baseUnitStep = options.baseUnitStep || 1,
                range = axis.range(options),
                max = range.max,
                date,
                nextDate,
                groups = [];

            for (date = range.min; date < max; date = nextDate) {
                groups.push(date);

                nextDate = addDuration(date, baseUnitStep, baseUnit, options.weekStartDay);
                if (nextDate > maxCategory && !options.max) {
                    break;
                }
            }

            if (!options.roundToBaseUnit && !dateEquals(last(groups), max)) {
                if (max < nextDate && options._collapse !== false) {
                    this._srcMaxDate = max;
                } else {
                    groups.push(max);
                }
            }

            options.srcCategories = categories;
            options.categories = groups;
        },

        createAxisLabel: function (index, labelOptions) {
            var options = this.options,
                dataItem = options.dataItems ? options.dataItems[index] : null,
                date = options.categories[index],
                baseUnit = options.baseUnit,
                visible = true,
                unitFormat = labelOptions.dateFormats[baseUnit];

            if (options.justified) {
                var roundedDate = floorDate(date, baseUnit, options.weekStartDay);
                visible = dateEquals(roundedDate, date);
            } else if (!options.roundToBaseUnit) {
                visible = !dateEquals(this.range().max, date);
            }

            if (visible) {
                labelOptions.format = labelOptions.format || unitFormat;
                var text = this.axisLabelText(date, dataItem, labelOptions);
                if (text) {
                    return new AxisLabel(date, text, index, dataItem, labelOptions);
                }
            }
        },

        categoryIndex: function (value, range) {
            var axis = this,
                options = axis.options,
                categories = options.categories,
                equalsRoundedMax,
                index;

            value = toDate(value);
            range = range || axis.range();
            equalsRoundedMax = options.roundToBaseUnit && dateEquals(range.max, value);
            if (value && (value > range.max || equalsRoundedMax)) {
                return categories.length;
            } else if (!value || value < range.min) {
                return -1;
            }

            index = lteDateIndex(value, categories);

            return index;
        },

        getSlot: function (a, b) {
            var axis = this;

            if (typeof a === OBJECT) {
                a = axis.categoryIndex(a);
            }

            if (typeof b === OBJECT) {
                b = axis.categoryIndex(b);
            }

            return CategoryAxis.fn.getSlot.call(axis, a, b);
        }
    });

    var DateValueAxis = Axis.extend({
        init: function (seriesMin, seriesMax, options) {
            var axis = this;

            options = options || {};

            deepExtend(options, {
                min: toDate(options.min),
                max: toDate(options.max),
                axisCrossingValue: toDate(
                    options.axisCrossingValues || options.axisCrossingValue
                )
            });

            options = axis.applyDefaults(toDate(seriesMin), toDate(seriesMax), options);

            Axis.fn.init.call(axis, options);
        },

        options: {
            type: DATE,
            majorGridLines: {
                visible: true,
                width: 1,
                color: BLACK
            },
            labels: {
                dateFormats: DateLabelFormats
            }
        },

        applyDefaults: function (seriesMin, seriesMax, options) {
            var axis = this,
                min = options.min || seriesMin,
                max = options.max || seriesMax,
                baseUnit = options.baseUnit || axis.timeUnits(max - min),
                baseUnitTime = TIME_PER_UNIT[baseUnit],
                autoMin = floorDate(toTime(min) - 1, baseUnit) || toDate(max),
                autoMax = ceilDate(toTime(max) + 1, baseUnit),
                userMajorUnit = options.majorUnit ? options.majorUnit : undefined,
                majorUnit = userMajorUnit || dataviz.ceil(
                                dataviz.autoMajorUnit(autoMin.getTime(), autoMax.getTime()),
                                baseUnitTime
                            ) / baseUnitTime,
                actualUnits = duration(autoMin, autoMax, baseUnit),
                totalUnits = dataviz.ceil(actualUnits, majorUnit),
                unitsToAdd = totalUnits - actualUnits,
                head = math.floor(unitsToAdd / 2),
                tail = unitsToAdd - head;

            if (!options.baseUnit) {
                delete options.baseUnit;
            }

            return deepExtend({
                baseUnit: baseUnit,
                min: addDuration(autoMin, -head, baseUnit),
                max: addDuration(autoMax, tail, baseUnit),
                minorUnit: majorUnit / 5
            }, options, {
                majorUnit: majorUnit
            }
            );
        },

        range: function () {
            var options = this.options;
            return { min: options.min, max: options.max };
        },

        getDivisions: function (stepValue) {
            var options = this.options;

            return math.floor(
                duration(options.min, options.max, options.baseUnit) / stepValue + 1
            );
        },

        getTickPositions: function (step) {
            var axis = this,
                options = axis.options,
                vertical = options.vertical,
                reverse = options.reverse,
                lineBox = axis.lineBox(),
                lineSize = vertical ? lineBox.height() : lineBox.width(),
                timeRange = duration(options.min, options.max, options.baseUnit),
                scale = lineSize / timeRange,
                scaleStep = step * scale,
                divisions = axis.getDivisions(step),
                dir = (vertical ? -1 : 1) * (reverse ? -1 : 1),
                startEdge = dir === 1 ? 1 : 2,
                pos = lineBox[(vertical ? Y : X) + startEdge],
                positions = [],
                i;

            for (i = 0; i < divisions; i++) {
                positions.push(round(pos, COORD_PRECISION));
                pos = pos + scaleStep * dir;
            }

            return positions;
        },

        getMajorTickPositions: function () {
            var axis = this;

            return axis.getTickPositions(axis.options.majorUnit);
        },

        getMinorTickPositions: function () {
            var axis = this;

            return axis.getTickPositions(axis.options.minorUnit);
        },

        getSlot: function (a, b, limit) {
            return NumericAxis.fn.getSlot.call(
                this, toDate(a), toDate(b), limit
            );
        },

        getValue: function (point) {
            var value = NumericAxis.fn.getValue.call(this, point);

            return value !== null ? toDate(value) : null;
        },

        labelsCount: function () {
            return this.getDivisions(this.options.majorUnit);
        },

        createAxisLabel: function (index, labelOptions) {
            var options = this.options,
                offset = index * options.majorUnit,
                date = addDuration(options.min, offset, options.baseUnit),
                unitFormat = labelOptions.dateFormats[options.baseUnit];

            labelOptions.format = labelOptions.format || unitFormat;

            var text = this.axisLabelText(date, null, labelOptions);
            return new AxisLabel(date, text, index, null, labelOptions);
        },

        timeUnits: function (delta) {
            var unit = HOURS;

            if (delta >= TIME_PER_YEAR) {
                unit = YEARS;
            } else if (delta >= TIME_PER_MONTH) {
                unit = MONTHS;
            } else if (delta >= TIME_PER_WEEK) {
                unit = WEEKS;
            } else if (delta >= TIME_PER_DAY) {
                unit = DAYS;
            }

            return unit;
        },

        translateRange: function (delta) {
            var axis = this,
                options = axis.options,
                baseUnit = options.baseUnit,
                weekStartDay = options.weekStartDay,
                lineBox = axis.lineBox(),
                size = options.vertical ? lineBox.height() : lineBox.width(),
                range = axis.range(),
                scale = size / (range.max - range.min),
                offset = round(delta / scale, DEFAULT_PRECISION),
                from = addTicks(options.min, offset),
                to = addTicks(options.max, offset);

            return {
                min: addDuration(from, 0, baseUnit, weekStartDay),
                max: addDuration(to, 0, baseUnit, weekStartDay)
            };
        },

        scaleRange: function (delta) {
            var axis = this,
                options = axis.options,
                rounds = math.abs(delta),
                from = options.min,
                to = options.max,
                range,
                step;

            while (rounds--) {
                range = dateDiff(from, to);
                step = math.round(range * 0.1);
                if (delta < 0) {
                    from = addTicks(from, step);
                    to = addTicks(to, -step);
                } else {
                    from = addTicks(from, -step);
                    to = addTicks(to, step);
                }
            }

            return { min: from, max: to };
        },

        shouldRenderNote: function (value) {
            var range = this.range();

            return dateComparer(value, range.min) >= 0 && dateComparer(value, range.max) <= 0;
        }
    });

    var ClusterLayout = ChartElement.extend({
        options: {
            vertical: false,
            gap: 0,
            spacing: 0
        },

        reflow: function (box) {
            var cluster = this,
                options = cluster.options,
                vertical = options.vertical,
                axis = vertical ? Y : X,
                children = cluster.children,
                gap = options.gap,
                spacing = options.spacing,
                count = children.length,
                slots = count + gap + (spacing * (count - 1)),
                slotSize = (vertical ? box.height() : box.width()) / slots,
                position = box[axis + 1] + slotSize * (gap / 2),
                childBox,
                i;

            for (i = 0; i < count; i++) {
                childBox = (children[i].box || box).clone();

                childBox[axis + 1] = position;
                childBox[axis + 2] = position + slotSize;

                children[i].reflow(childBox);
                if (i < count - 1) {
                    position += (slotSize * spacing);
                }

                position += slotSize;
            }
        },

        createVisual: noop
    });

    var StackWrap = ChartElement.extend({
        options: {
            vertical: true
        },

        reflow: function (targetBox) {
            var options = this.options,
                vertical = options.vertical,
                positionAxis = vertical ? X : Y,
                stackAxis = vertical ? Y : X,
                stackBase = targetBox[stackAxis + 2],
                children = this.children,
                box = this.box = new Box2D(),
                childrenCount = children.length,
                i;

            for (i = 0; i < childrenCount; i++) {
                var currentChild = children[i],
                    childBox;
                if (currentChild.visible !== false) {
                    childBox = currentChild.box.clone();
                    childBox.snapTo(targetBox, positionAxis);
                    if (currentChild.options) {
                        // TODO: Remove stackBase and fix BarAnimation
                        currentChild.options.stackBase = stackBase;
                    }

                    if (i === 0) {
                        box = this.box = childBox.clone();
                    }

                    currentChild.reflow(childBox);
                    box.wrap(childBox);
                }
            }
        },

        createVisual: noop
    });

    var PointEventsMixin = {
        click: function (chart, e) {
            var point = this;

            chart.trigger(SERIES_CLICK, {
                value: point.value,
                percentage: point.percentage,
                category: point.category,
                series: point.series,
                dataItem: point.dataItem,
                runningTotal: point.runningTotal,
                total: point.total,
                element: $(e.target),
                originalEvent: e
            });
        },

        hover: function (chart, e) {
            var point = this;

            return chart.trigger(SERIES_HOVER, {
                value: point.value,
                percentage: point.percentage,
                category: point.category,
                series: point.series,
                dataItem: point.dataItem,
                runningTotal: point.runningTotal,
                total: point.total,
                element: $(e.target),
                originalEvent: e
            });
        }
    };

    var NoteMixin = {
        createNote: function () {
            var element = this,
                options = element.options.notes,
                text = element.noteText || options.label.text;

            if (options.visible !== false && defined(text) && text !== null) {
                element.note = new Note(
                    element.value,
                    text,
                    element.dataItem,
                    element.category,
                    element.series,
                    element.options.notes
                );
                element.append(element.note);
            }
        }
    };

    var Bar = ChartElement.extend({
        init: function (value, options) {
            var bar = this;

            ChartElement.fn.init.call(bar);

            bar.options = options;
            bar.color = options.color || WHITE;
            bar.aboveAxis = valueOrDefault(bar.options.aboveAxis, true);
            bar.value = value;
        },

        defaults: {
            border: {
                width: 1
            },
            vertical: true,
            overlay: {
                gradient: GLASS
            },
            labels: {
                visible: false,
                format: "{0}"
            },
            animation: {
                type: BAR
            },
            opacity: 1,
            notes: {
                label: {}
            }
        },

        render: function () {
            if (this._rendered) {
                return;
            } else {
                this._rendered = true;
            }

            this.createLabel();
            this.createNote();

            if (this.errorBar) {
                this.append(this.errorBar);
            }
        },

        createLabel: function () {
            var value = this.value;
            var options = this.options;
            var labels = options.labels;
            var labelText;

            if (labels.visible) {
                if (labels.template) {
                    var labelTemplate = template(labels.template);
                    labelText = labelTemplate({
                        dataItem: this.dataItem,
                        category: this.category,
                        value: this.value,
                        percentage: this.percentage,
                        runningTotal: this.runningTotal,
                        total: this.total,
                        series: this.series
                    });
                } else {
                    labelText = this.formatValue(labels.format);
                }

                this.label = new BarLabel(labelText,
                        deepExtend({
                            vertical: options.vertical
                        },
                        options.labels
                    ));
                this.append(this.label);
            }
        },

        formatValue: function (format) {
            return this.owner.formatPointValue(this, format);
        },

        reflow: function (targetBox) {
            this.render();

            var bar = this,
                options = bar.options,
                label = bar.label;

            bar.box = targetBox;

            if (label) {
                label.options.aboveAxis = bar.aboveAxis;
                label.reflow(targetBox);
            }

            if (bar.note) {
                bar.note.reflow(targetBox);
            }

            if (bar.errorBars) {
                for (var i = 0; i < bar.errorBars.length; i++) {
                    bar.errorBars[i].reflow(targetBox);
                }
            }
        },

        createVisual: function () {
            var box = this.box;
            if (this.visible !== false) {
                ChartElement.fn.createVisual.call(this);
                if (box.width() > 0 && box.height() > 0) {
                    this.createRect();
                }
            }
        },

        createRect: function (view) {
            var options = this.options;
            var border = options.border;
            var strokeOpacity = defined(border.opacity) ? border.opacity : options.opacity;
            var rect = draw.Path.fromRect(this.box.toRect(), {
                fill: {
                    color: this.color,
                    opacity: options.opacity
                },
                stroke: {
                    color: this.getBorderColor(),
                    width: border.width,
                    opacity: strokeOpacity,
                    dashType: border.dashType
                }
            });

            alignPathToPixel(rect);
            this.visual.append(rect);

            if (hasGradientOverlay(options)) {
                this.visual.append(this.createGradientOverlay(rect, {
                    baseColor: this.color
                }, deepExtend({
                    end: !options.vertical ? [0, 1] : undefined
                }, options.overlay)
                ));
            }
        },

        createAnimation: function () {
            var options = this.options;

            deepExtend(options, {
                animation: {
                    aboveAxis: this.aboveAxis,
                    vertical: options.vertical,
                    stackBase: options.stackBase
                }
            });

            ChartElement.fn.createAnimation.call(this);
        },

        createHighlight: function (style) {
            var highlight = draw.Path.fromRect(this.box.toRect(), style);

            return alignPathToPixel(highlight);
        },

        getBorderColor: function () {
            var bar = this,
                options = bar.options,
                color = bar.color,
                border = options.border,
                borderColor = border.color,
                brightness = border._brightness || BAR_BORDER_BRIGHTNESS;

            if (!defined(borderColor)) {
                borderColor =
                    new Color(color).brightness(brightness).toHex();
            }

            return borderColor;
        },

        tooltipAnchor: function (tooltipWidth, tooltipHeight) {
            var bar = this,
                options = bar.options,
                box = bar.box,
                vertical = options.vertical,
                aboveAxis = bar.aboveAxis,
                clipBox = bar.owner.pane.clipBox() || box,
                x,
                y;

            if (vertical) {
                x = box.x2 + TOOLTIP_OFFSET;
                y = aboveAxis ? math.max(box.y1, clipBox.y1) : math.min(box.y2, clipBox.y2) - tooltipHeight;
            } else {
                var x1 = math.max(box.x1, clipBox.x1),
                    x2 = math.min(box.x2, clipBox.x2);
                if (options.isStacked) {
                    x = aboveAxis ? x2 - tooltipWidth : x1;
                    y = box.y1 - tooltipHeight - TOOLTIP_OFFSET;
                } else {
                    x = aboveAxis ? x2 + TOOLTIP_OFFSET : x1 - tooltipWidth - TOOLTIP_OFFSET;
                    y = box.y1;
                }
            }

            return new Point2D(x, y);
        }
    });
    deepExtend(Bar.fn, PointEventsMixin);
    deepExtend(Bar.fn, NoteMixin);

    var BarAnimation = draw.Animation.extend({
        options: {
            duration: INITIAL_ANIMATION_DURATION
        },

        setup: function () {
            var element = this.element;
            var options = this.options;

            var bbox = element.bbox();
            if (bbox) {
                var origin = this.origin = options.aboveAxis ?
                    bbox.bottomLeft() : bbox.topRight();

                var axis = options.vertical ? Y : X;
                var stackBase = options.stackBase;
                var fromOffset = this.fromOffset = new geom.Point();
                fromOffset[axis] = valueOrDefault(stackBase, origin[axis]) - origin[axis];

                var fromScale = this.fromScale = new geom.Point(1, 1);
                fromScale[axis] = 0;

                element.transform(geom.transform()
                    .scale(fromScale.x, fromScale.y)
                );
            } else {
                this.abort();
            }
        },

        step: function (pos) {
            var scaleX = interpolate(this.fromScale.x, 1, pos);
            var scaleY = interpolate(this.fromScale.y, 1, pos);
            var translateX = interpolate(this.fromOffset.x, 0, pos);
            var translateY = interpolate(this.fromOffset.y, 0, pos);

            this.element.transform(geom.transform()
                .translate(translateX, translateY)
                .scale(scaleX, scaleY, this.origin)
            );
        }
    });
    draw.AnimationFactory.current.register(BAR, BarAnimation);

    var FadeInAnimation = draw.Animation.extend({
        options: {
            duration: 200,
            easing: LINEAR
        },

        setup: function () {
            this.fadeTo = this.element.opacity();
            this.element.opacity(0);
        },

        step: function (pos) {
            this.element.opacity(pos * this.fadeTo);
        }
    });
    draw.AnimationFactory.current.register(FADEIN, FadeInAnimation);


    var ErrorRangeCalculator = function (errorValue, series, field) {
        var that = this;
        that.errorValue = errorValue;
        that.initGlobalRanges(errorValue, series, field);
    };

    ErrorRangeCalculator.prototype = ErrorRangeCalculator.fn = {
        percentRegex: /percent(?:\w*)\((\d+)\)/,
        standardDeviationRegex: new RegExp("^" + STD_DEV + "(?:\\((\\d+(?:\\.\\d+)?)\\))?$"),

        initGlobalRanges: function (errorValue, series, field) {
            var that = this,
                data = series.data,
                deviationMatch = that.standardDeviationRegex.exec(errorValue);

            if (deviationMatch) {
                that.valueGetter = that.createValueGetter(series, field);

                var average = that.getAverage(data),
                    deviation = that.getStandardDeviation(data, average, false),
                    multiple = deviationMatch[1] ? parseFloat(deviationMatch[1]) : 1,
                    errorRange = { low: average.value - deviation * multiple, high: average.value + deviation * multiple };
                that.globalRange = function () {
                    return errorRange;
                };
            } else if (errorValue.indexOf && errorValue.indexOf(STD_ERR) >= 0) {
                that.valueGetter = that.createValueGetter(series, field);
                var standardError = that.getStandardError(data, that.getAverage(data));
                that.globalRange = function (value) {
                    return { low: value - standardError, high: value + standardError };
                };
            }
        },

        createValueGetter: function (series, field) {
            var data = series.data,
                binder = SeriesBinder.current,
                valueFields = binder.valueFields(series),
                item = defined(data[0]) ? data[0] : {},
                idx,
                srcValueFields,
                valueGetter;

            if (isArray(item)) {
                idx = field ? indexOf(field, valueFields) : 0;
                valueGetter = getter("[" + idx + "]");
            } else if (isNumber(item)) {
                valueGetter = getter();
            } else if (typeof item === OBJECT) {
                srcValueFields = binder.sourceFields(series, valueFields);
                valueGetter = getter(srcValueFields[indexOf(field, valueFields)]);
            }

            return valueGetter;
        },

        getErrorRange: function (pointValue) {
            var that = this,
                errorValue = that.errorValue,
                low,
                high,
                value;

            if (!defined(errorValue)) {
                return;
            }

            if (that.globalRange) {
                return that.globalRange(pointValue);
            }

            if (isArray(errorValue)) {
                low = pointValue - errorValue[0];
                high = pointValue + errorValue[1];
            } else if (isNumber(value = parseFloat(errorValue))) {
                low = pointValue - value;
                high = pointValue + value;
            } else if ((value = that.percentRegex.exec(errorValue))) {
                var percentValue = pointValue * (parseFloat(value[1]) / 100);
                low = pointValue - math.abs(percentValue);
                high = pointValue + math.abs(percentValue);
            } else {
                throw new Error("Invalid ErrorBar value: " + errorValue);
            }

            return { low: low, high: high };
        },

        getStandardError: function (data, average) {
            return this.getStandardDeviation(data, average, true) / math.sqrt(average.count);
        },

        getStandardDeviation: function (data, average, isSample) {
            var squareDifferenceSum = 0,
                length = data.length,
                total = isSample ? average.count - 1 : average.count,
                value;

            for (var i = 0; i < length; i++) {
                value = this.valueGetter(data[i]);
                if (isNumber(value)) {
                    squareDifferenceSum += math.pow(value - average.value, 2);
                }
            }

            return math.sqrt(squareDifferenceSum / total);
        },

        getAverage: function (data) {
            var sum = 0,
                count = 0,
                length = data.length,
                value;

            for (var i = 0; i < length; i++) {
                value = this.valueGetter(data[i]);
                if (isNumber(value)) {
                    sum += value;
                    count++;
                }
            }

            return {
                value: sum / count,
                count: count
            };
        }
    };

    var CategoricalChart = ChartElement.extend({
        init: function (plotArea, options) {
            var chart = this;

            ChartElement.fn.init.call(chart, options);

            chart.plotArea = plotArea;
            chart.categoryAxis = plotArea.seriesCategoryAxis(options.series[0]);

            // Value axis ranges grouped by axis name, e.g.:
            // primary: { min: 0, max: 1 }
            chart.valueAxisRanges = {};

            chart.points = [];
            chart.categoryPoints = [];
            chart.seriesPoints = [];
            chart.seriesOptions = [];
            chart._evalSeries = [];

            chart.render();
        },

        options: {
            series: [],
            invertAxes: false,
            isStacked: false,
            clip: true
        },

        render: function () {
            var chart = this;
            chart.traverseDataPoints(proxy(chart.addValue, chart));
        },

        pointOptions: function (series, seriesIx) {
            var options = this.seriesOptions[seriesIx];
            if (!options) {
                var defaults = this.pointType().fn.defaults;
                this.seriesOptions[seriesIx] = options = deepExtend({}, defaults, {
                    vertical: !this.options.invertAxes
                }, series);
            }

            return options;
        },

        plotValue: function (point) {
            if (!point) {
                return 0;
            }

            if (this.options.isStacked100 && isNumber(point.value)) {
                var categoryIx = point.categoryIx;
                var categoryPts = this.categoryPoints[categoryIx];
                var categorySum = 0;

                for (var i = 0; i < categoryPts.length; i++) {
                    var other = categoryPts[i];
                    if (other) {
                        var stack = point.series.stack;
                        var otherStack = other.series.stack;

                        if ((stack && otherStack) && stack.group !== otherStack.group) {
                            continue;
                        }

                        if (isNumber(other.value)) {
                            categorySum += math.abs(other.value);
                        }
                    }
                }

                if (categorySum > 0) {
                    return point.value / categorySum;
                }
            }

            return point.value;
        },

        plotRange: function (point, startValue) {
            var categoryIx = point.categoryIx;
            var categoryPts = this.categoryPoints[categoryIx];

            if (this.options.isStacked) {
                startValue = startValue || 0;
                var plotValue = this.plotValue(point);
                var positive = plotValue > 0;
                var prevValue = startValue;
                var isStackedBar = false;

                for (var i = 0; i < categoryPts.length; i++) {
                    var other = categoryPts[i];

                    if (point === other) {
                        break;
                    }

                    var stack = point.series.stack;
                    var otherStack = other.series.stack;
                    if (stack && otherStack) {
                        if (typeof stack === STRING && stack !== otherStack) {
                            continue;
                        }

                        if (stack.group && stack.group !== otherStack.group) {
                            continue;
                        }
                    }

                    var otherValue = this.plotValue(other);
                    if ((otherValue > 0 && positive) ||
                        (otherValue < 0 && !positive)) {
                        prevValue += otherValue;
                        plotValue += otherValue;
                        isStackedBar = true;
                    }
                }

                if (isStackedBar) {
                    prevValue -= startValue;
                }

                return [prevValue, plotValue];
            }

            var series = point.series;
            var valueAxis = this.seriesValueAxis(series);
            var axisCrossingValue = this.categoryAxisCrossingValue(valueAxis);

            return [axisCrossingValue, point.value || axisCrossingValue];
        },

        stackLimits: function (axisName, stackName) {
            var min = MAX_VALUE;
            var max = MIN_VALUE;

            for (var i = 0; i < this.categoryPoints.length; i++) {
                var categoryPts = this.categoryPoints[i];

                for (var pIx = 0; pIx < categoryPts.length; pIx++) {
                    var point = categoryPts[pIx];
                    if (point) {
                        if (point.series.stack === stackName || point.series.axis === axisName) {
                            var to = this.plotRange(point, 0)[1];
                            if (defined(to)) {
                                max = math.max(max, to);
                                min = math.min(min, to);
                            }
                        }
                    }
                }
            }

            return { min: min, max: max };
        },

        updateStackRange: function () {
            var chart = this;
            var chartSeries = chart.options.series;
            var isStacked = chart.options.isStacked;
            var limits;
            var limitsCache = {};

            if (isStacked) {
                for (var i = 0; i < chartSeries.length; i++) {
                    var series = chartSeries[i];
                    var axisName = series.axis;
                    var key = axisName + series.stack;

                    limits = limitsCache[key];
                    if (!limits) {
                        limits = chart.stackLimits(axisName, series.stack);

                        var errorTotals = chart.errorTotals;
                        if (errorTotals) {
                            if (errorTotals.negative.length) {
                                limits.min = math.min(limits.min, sparseArrayMin(errorTotals.negative));
                            }
                            if (errorTotals.positive.length) {
                                limits.max = math.max(limits.max, sparseArrayMax(errorTotals.positive));
                            }
                        }

                        limitsCache[key] = limits;
                    }

                    chart.valueAxisRanges[axisName] = limits;
                }
            }
        },

        addErrorBar: function (point, data, categoryIx) {
            var chart = this,
                value = point.value,
                series = point.series,
                seriesIx = point.seriesIx,
                errorBars = point.options.errorBars,
                errorRange,
                lowValue = data.fields[ERROR_LOW_FIELD],
                highValue = data.fields[ERROR_HIGH_FIELD];

            if (isNumber(lowValue) &&
                isNumber(highValue)) {
                errorRange = { low: lowValue, high: highValue };
            } else if (errorBars && defined(errorBars.value)) {
                chart.seriesErrorRanges = chart.seriesErrorRanges || [];
                chart.seriesErrorRanges[seriesIx] = chart.seriesErrorRanges[seriesIx] ||
                    new ErrorRangeCalculator(errorBars.value, series, VALUE);

                errorRange = chart.seriesErrorRanges[seriesIx].getErrorRange(value);
            }

            if (errorRange) {
                point.low = errorRange.low;
                point.high = errorRange.high;
                chart.addPointErrorBar(point, categoryIx);
            }
        },

        addPointErrorBar: function (point, categoryIx) {
            var chart = this,
                series = point.series,
                low = point.low,
                high = point.high,
                isVertical = !chart.options.invertAxes,
                options = point.options.errorBars,
                errorBar,
                stackedErrorRange;

            if (chart.options.isStacked) {
                stackedErrorRange = chart.stackedErrorRange(point, categoryIx);
                low = stackedErrorRange.low;
                high = stackedErrorRange.high;
            } else {
                var fields = { categoryIx: categoryIx, series: series };
                chart.updateRange({ value: low }, fields);
                chart.updateRange({ value: high }, fields);
            }

            errorBar = new CategoricalErrorBar(low, high, isVertical, chart, series, options);
            point.errorBars = [errorBar];
            point.append(errorBar);
        },

        stackedErrorRange: function (point, categoryIx) {
            var chart = this,
                value = point.value,
                plotValue = chart.plotRange(point, 0)[1] - point.value,
                low = point.low + plotValue,
                high = point.high + plotValue;

            chart.errorTotals = chart.errorTotals || { positive: [], negative: [] };

            if (low < 0) {
                chart.errorTotals.negative[categoryIx] = math.min(chart.errorTotals.negative[categoryIx] || 0, low);
            }

            if (high > 0) {
                chart.errorTotals.positive[categoryIx] = math.max(chart.errorTotals.positive[categoryIx] || 0, high);
            }

            return { low: low, high: high };
        },

        addValue: function (data, fields) {
            var chart = this;
            var categoryIx = fields.categoryIx;
            var category = fields.category;
            var series = fields.series;
            var seriesIx = fields.seriesIx;

            var categoryPoints = chart.categoryPoints[categoryIx];
            if (!categoryPoints) {
                chart.categoryPoints[categoryIx] = categoryPoints = [];
            }

            var seriesPoints = chart.seriesPoints[seriesIx];
            if (!seriesPoints) {
                chart.seriesPoints[seriesIx] = seriesPoints = [];
            }

            var point = chart.createPoint(data, fields);
            if (point) {
                $.extend(point, fields);

                point.owner = chart;
                point.dataItem = series.data[categoryIx];
                point.noteText = data.fields.noteText;
                chart.addErrorBar(point, data, categoryIx);
            }

            chart.points.push(point);
            seriesPoints.push(point);
            categoryPoints.push(point);

            chart.updateRange(data.valueFields, fields);
        },

        evalPointOptions: function (options, value, category, categoryIx, series, seriesIx) {
            var state = { defaults: series._defaults, excluded: ["data", "aggregate", "_events", "tooltip", "template"] };

            var doEval = this._evalSeries[seriesIx];
            if (!defined(doEval)) {
                this._evalSeries[seriesIx] = doEval = evalOptions(options, {}, state, true);
            }

            if (doEval) {
                options = deepExtend({}, options);
                evalOptions(options, {
                    value: value,
                    category: category,
                    index: categoryIx,
                    series: series,
                    dataItem: series.data[categoryIx]
                }, state);
            }

            return options;
        },

        updateRange: function (data, fields) {
            var chart = this,
                axisName = fields.series.axis,
                value = data.value,
                axisRange = chart.valueAxisRanges[axisName];

            if (isFinite(value) && value !== null) {
                axisRange = chart.valueAxisRanges[axisName] =
                    axisRange || { min: MAX_VALUE, max: MIN_VALUE };

                axisRange.min = math.min(axisRange.min, value);
                axisRange.max = math.max(axisRange.max, value);
            }
        },

        seriesValueAxis: function (series) {
            var plotArea = this.plotArea,
                axisName = series.axis,
                axis = axisName ?
                    plotArea.namedValueAxes[axisName] :
                    plotArea.valueAxis;

            if (!axis) {
                throw new Error("Unable to locate value axis with name " + axisName);
            }

            return axis;
        },

        reflow: function (targetBox) {
            var chart = this,
                pointIx = 0,
                categorySlots = chart.categorySlots = [],
                chartPoints = chart.points,
                categoryAxis = chart.categoryAxis,
                value, valueAxis,
                point;

            chart.traverseDataPoints(function (data, fields) {
                var category = fields.category;
                var categoryIx = fields.categoryIx;
                var currentSeries = fields.series;

                value = chart.pointValue(data);

                valueAxis = chart.seriesValueAxis(currentSeries);
                point = chartPoints[pointIx++];

                var categorySlot = categorySlots[categoryIx];
                if (!categorySlot) {
                    categorySlots[categoryIx] = categorySlot =
                        chart.categorySlot(categoryAxis, categoryIx, valueAxis);
                }

                if (point) {
                    var plotRange = chart.plotRange(point, valueAxis.startValue());
                    var valueSlot = valueAxis.getSlot(plotRange[0], plotRange[1], !chart.options.clip);
                    if (valueSlot) {
                        var pointSlot = chart.pointSlot(categorySlot, valueSlot);

                        point.aboveAxis = chart.aboveAxis(point, valueAxis);
                        if (chart.options.isStacked100) {
                            point.percentage = chart.plotValue(point);
                        }

                        chart.reflowPoint(point, pointSlot);
                    } else {
                        point.visible = false;
                    }
                }
            });

            chart.reflowCategories(categorySlots);

            chart.box = targetBox;
        },

        aboveAxis: function (point, valueAxis) {
            var axisCrossingValue = this.categoryAxisCrossingValue(valueAxis);
            var value = point.value;

            return valueAxis.options.reverse ?
                value < axisCrossingValue : value >= axisCrossingValue;
        },

        categoryAxisCrossingValue: function (valueAxis) {
            var categoryAxis = this.categoryAxis,
                options = valueAxis.options,
                crossingValues = [].concat(
                    options.axisCrossingValues || options.axisCrossingValue
                );

            return crossingValues[categoryAxis.axisIndex || 0] || 0;
        },

        reflowPoint: function (point, pointSlot) {
            point.reflow(pointSlot);
        },

        reflowCategories: function () { },

        pointSlot: function (categorySlot, valueSlot) {
            var chart = this,
                options = chart.options,
                invertAxes = options.invertAxes,
                slotX = invertAxes ? valueSlot : categorySlot,
                slotY = invertAxes ? categorySlot : valueSlot;

            return new Box2D(slotX.x1, slotY.y1, slotX.x2, slotY.y2);
        },

        categorySlot: function (categoryAxis, categoryIx) {
            return categoryAxis.getSlot(categoryIx);
        },

        traverseDataPoints: function (callback) {
            var chart = this,
                options = chart.options,
                series = options.series,
                categories = chart.categoryAxis.options.categories || [],
                count = categoriesCount(series),
                categoryIx,
                seriesIx,
                pointData,
                currentCategory,
                currentSeries,
                seriesCount = series.length;

            for (categoryIx = 0; categoryIx < count; categoryIx++) {
                for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
                    currentSeries = series[seriesIx];
                    currentCategory = categories[categoryIx];
                    pointData = this._bindPoint(currentSeries, seriesIx, categoryIx);

                    callback(pointData, {
                        category: currentCategory,
                        categoryIx: categoryIx,
                        series: currentSeries,
                        seriesIx: seriesIx
                    });
                }
            }
        },

        _bindPoint: function (series, seriesIx, categoryIx) {
            if (!this._bindCache) {
                this._bindCache = [];
            }

            var bindCache = this._bindCache[seriesIx];
            if (!bindCache) {
                bindCache = this._bindCache[seriesIx] = [];
            }

            var data = bindCache[categoryIx];
            if (!data) {
                data = bindCache[categoryIx] = SeriesBinder.current.bindPoint(series, categoryIx);
            }

            return data;
        },

        formatPointValue: function (point, format) {
            if (point.value === null) {
                return "";
            }

            return autoFormat(format, point.value);
        },

        pointValue: function (data) {
            return data.valueFields.value;
        },

        createVisual: noop
    });

    var BarChart = CategoricalChart.extend({
        render: function () {
            var chart = this;

            CategoricalChart.fn.render.apply(chart);
            chart.updateStackRange();
        },

        pointType: function () {
            return Bar;
        },

        clusterType: function () {
            return ClusterLayout;
        },

        stackType: function () {
            return StackWrap;
        },

        stackLimits: function (axisName, stackName) {
            var limits = CategoricalChart.fn.stackLimits.call(this, axisName, stackName);
            limits.min = math.min(0, limits.min);
            limits.max = math.max(0, limits.max);

            return limits;
        },

        createPoint: function (data, fields) {
            var chart = this;
            var categoryIx = fields.categoryIx;
            var category = fields.category;
            var series = fields.series;
            var seriesIx = fields.seriesIx;
            var value = chart.pointValue(data);
            var options = chart.options;
            var children = chart.children;
            var isStacked = chart.options.isStacked;
            var point;
            var pointType = chart.pointType();
            var pointOptions;
            var cluster;
            var clusterType = chart.clusterType();

            pointOptions = this.pointOptions(series, seriesIx);

            var labelOptions = pointOptions.labels;
            if (isStacked) {
                if (labelOptions.position == OUTSIDE_END) {
                    labelOptions.position = INSIDE_END;
                }
            }

            pointOptions.isStacked = isStacked;

            var color = data.fields.color || series.color;
            if (value < 0 && pointOptions.negativeColor) {
                color = pointOptions.negativeColor;
            }

            pointOptions = chart.evalPointOptions(
                pointOptions, value, category, categoryIx, series, seriesIx
            );

            if (kendo.isFunction(series.color)) {
                color = pointOptions.color;
            }

            point = new pointType(value, pointOptions);
            point.color = color;

            cluster = children[categoryIx];
            if (!cluster) {
                cluster = new clusterType({
                    vertical: options.invertAxes,
                    gap: options.gap,
                    spacing: options.spacing
                });
                chart.append(cluster);
            }

            if (isStacked) {
                var stackWrap = chart.getStackWrap(series, cluster);
                stackWrap.append(point);
            } else {
                cluster.append(point);
            }

            return point;
        },

        getStackWrap: function (series, cluster) {
            var stack = series.stack;
            var stackGroup = stack ? stack.group || stack : stack;

            var wraps = cluster.children;
            var stackWrap;
            if (typeof stackGroup === STRING) {
                for (var i = 0; i < wraps.length; i++) {
                    if (wraps[i]._stackGroup === stackGroup) {
                        stackWrap = wraps[i];
                        break;
                    }
                }
            } else {
                stackWrap = wraps[0];
            }

            if (!stackWrap) {
                var stackType = this.stackType();
                stackWrap = new stackType({
                    vertical: !this.options.invertAxes
                });
                stackWrap._stackGroup = stackGroup;
                cluster.append(stackWrap);
            }

            return stackWrap;
        },

        categorySlot: function (categoryAxis, categoryIx, valueAxis) {
            var chart = this,
                options = chart.options,
                categorySlot = categoryAxis.getSlot(categoryIx),
                startValue = valueAxis.startValue(),
                stackAxis, zeroSlot;

            if (options.isStacked) {
                zeroSlot = valueAxis.getSlot(startValue, startValue, true);
                stackAxis = options.invertAxes ? X : Y;
                categorySlot[stackAxis + 1] = categorySlot[stackAxis + 2] = zeroSlot[stackAxis + 1];
            }

            return categorySlot;
        },

        reflowCategories: function (categorySlots) {
            var chart = this,
                children = chart.children,
                childrenLength = children.length,
                i;

            for (i = 0; i < childrenLength; i++) {
                children[i].reflow(categorySlots[i]);
            }
        }
    });

    var RangeBar = Bar.extend({
        defaults: {
            labels: {
                format: "{0} - {1}"
            },
            tooltip: {
                format: "{1}"
            }
        },

        createLabel: function () {
            var value = this.value;
            var labels = this.options.labels;
            var fromOptions = deepExtend({}, labels, labels.from);
            var toOptions = deepExtend({}, labels, labels.to);

            if (fromOptions.visible) {
                this.labelFrom = this._createLabel(fromOptions);
                this.append(this.labelFrom);
            }

            if (toOptions.visible) {
                this.labelTo = this._createLabel(toOptions);
                this.append(this.labelTo);
            }
        },

        _createLabel: function (options) {
            var labelText;

            if (options.template) {
                var labelTemplate = template(options.template);
                labelText = labelTemplate({
                    dataItem: this.dataItem,
                    category: this.category,
                    value: this.value,
                    percentage: this.percentage,
                    runningTotal: this.runningTotal,
                    total: this.total,
                    series: this.series
                });
            } else {
                labelText = this.formatValue(options.format);
            }

            return new BarLabel(labelText,
                deepExtend({
                    vertical: this.options.vertical
                },
                options
            ));
        },

        reflow: function (targetBox) {
            this.render();

            var rangeBar = this,
                options = rangeBar.options,
                labelFrom = rangeBar.labelFrom,
                labelTo = rangeBar.labelTo,
                value = rangeBar.value;

            rangeBar.box = targetBox;

            if (labelFrom) {
                labelFrom.options.aboveAxis = rangeBar.value.from > rangeBar.value.to;
                labelFrom.reflow(targetBox);
            }

            if (labelTo) {
                labelTo.options.aboveAxis = rangeBar.value.to > rangeBar.value.from;
                labelTo.reflow(targetBox);
            }

            if (rangeBar.note) {
                rangeBar.note.reflow(targetBox);
            }
        }
    });

    var RangeBarChart = BarChart.extend({
        pointType: function () {
            return RangeBar;
        },

        pointValue: function (data) {
            return data.valueFields;
        },

        formatPointValue: function (point, format) {
            if (point.value.from === null && point.value.to === null) {
                return "";
            }

            return autoFormat(format, point.value.from, point.value.to);
        },

        plotLimits: CategoricalChart.fn.plotLimits,

        plotRange: function (point) {
            if (!point) {
                return 0;
            }

            return [point.value.from, point.value.to];
        },

        updateRange: function (value, fields) {
            var chart = this,
                axisName = fields.series.axis,
                from = value.from,
                to = value.to,
                axisRange = chart.valueAxisRanges[axisName];

            if (value !== null && isNumber(from) && isNumber(to)) {
                axisRange = chart.valueAxisRanges[axisName] =
                    axisRange || { min: MAX_VALUE, max: MIN_VALUE };

                axisRange.min = math.min(axisRange.min, from);
                axisRange.max = math.max(axisRange.max, from);

                axisRange.min = math.min(axisRange.min, to);
                axisRange.max = math.max(axisRange.max, to);
            }
        },

        aboveAxis: function (point) {
            var value = point.value;
            return value.from < value.to;
        }
    });

    var BulletChart = CategoricalChart.extend({
        init: function (plotArea, options) {
            var chart = this;

            chart.wrapData(options);

            CategoricalChart.fn.init.call(chart, plotArea, options);
        },

        wrapData: function (options) {
            var series = options.series,
                i, data, seriesItem;

            for (i = 0; i < series.length; i++) {
                seriesItem = series[i];
                data = seriesItem.data;
                if (data && !isArray(data[0]) && typeof (data[0]) != OBJECT) {
                    seriesItem.data = [data];
                }
            }
        },

        reflowCategories: function (categorySlots) {
            var chart = this,
                children = chart.children,
                childrenLength = children.length,
                i;

            for (i = 0; i < childrenLength; i++) {
                children[i].reflow(categorySlots[i]);
            }
        },

        plotRange: function (point) {
            var series = point.series;
            var valueAxis = this.seriesValueAxis(series);
            var axisCrossingValue = this.categoryAxisCrossingValue(valueAxis);

            return [axisCrossingValue, point.value.current || axisCrossingValue];
        },

        createPoint: function (data, fields) {
            var chart = this;
            var categoryIx = fields.categoryIx;
            var category = fields.category;
            var series = fields.series;
            var seriesIx = fields.seriesIx;
            var value = data.valueFields;
            var options = chart.options;
            var children = chart.children;
            var bullet;
            var bulletOptions;
            var cluster;

            bulletOptions = deepExtend({
                vertical: !options.invertAxes,
                overlay: series.overlay,
                categoryIx: categoryIx,
                invertAxes: options.invertAxes
            }, series);

            bulletOptions = chart.evalPointOptions(
                bulletOptions, value, category, categoryIx, series, seriesIx
            );

            bullet = new Bullet(value, bulletOptions);

            cluster = children[categoryIx];
            if (!cluster) {
                cluster = new ClusterLayout({
                    vertical: options.invertAxes,
                    gap: options.gap,
                    spacing: options.spacing
                });
                chart.append(cluster);
            }

            cluster.append(bullet);

            return bullet;
        },

        updateRange: function (value, fields) {
            var chart = this,
                axisName = fields.series.axis,
                current = value.current,
                target = value.target,
                axisRange = chart.valueAxisRanges[axisName];

            if (defined(current) && !isNaN(current) && defined(target && !isNaN(target))) {
                axisRange = chart.valueAxisRanges[axisName] =
                    axisRange || { min: MAX_VALUE, max: MIN_VALUE };

                axisRange.min = math.min.apply(math, [axisRange.min, current, target]);
                axisRange.max = math.max.apply(math, [axisRange.max, current, target]);
            }
        },

        formatPointValue: function (point, format) {
            return autoFormat(format, point.value.current, point.value.target);
        },

        pointValue: function (data) {
            return data.valueFields.current;
        },

        aboveAxis: function (point, valueAxis) {
            var value = point.value.current;

            return value > 0;
        }
    });

    var Bullet = ChartElement.extend({
        init: function (value, options) {
            var bullet = this;

            ChartElement.fn.init.call(bullet, options);

            bullet.value = value;
            bullet.aboveAxis = bullet.options.aboveAxis;
        },

        options: {
            color: WHITE,
            border: {
                width: 1
            },
            vertical: false,
            animation: {
                type: BAR
            },
            opacity: 1,
            target: {
                shape: "",
                border: {
                    width: 0,
                    color: "green"
                },
                line: {
                    width: 2
                }
            },
            tooltip: {
                format: "Current: {0}</br>Target: {1}"
            }
        },

        render: function () {
            var bullet = this,
                options = bullet.options;

            if (!bullet._rendered) {
                bullet._rendered = true;

                if (defined(bullet.value.target)) {
                    bullet.target = new Target({
                        type: options.target.shape,
                        background: options.target.color || options.color,
                        opacity: options.opacity,
                        zIndex: options.zIndex,
                        border: options.target.border,
                        vAlign: TOP,
                        align: RIGHT
                    });

                    bullet.append(bullet.target);
                }

                bullet.createNote();
            }
        },

        reflow: function (box) {
            this.render();

            var bullet = this,
                options = bullet.options,
                chart = bullet.owner,
                target = bullet.target,
                invertAxes = options.invertAxes,
                valueAxis = chart.seriesValueAxis(bullet.options),
                categorySlot = chart.categorySlot(chart.categoryAxis, options.categoryIx, valueAxis),
                targetValueSlot = valueAxis.getSlot(bullet.value.target),
                targetSlotX = invertAxes ? targetValueSlot : categorySlot,
                targetSlotY = invertAxes ? categorySlot : targetValueSlot,
                targetSlot;

            if (target) {
                targetSlot = new Box2D(
                    targetSlotX.x1, targetSlotY.y1,
                    targetSlotX.x2, targetSlotY.y2
                );
                target.options.height = invertAxes ? targetSlot.height() : options.target.line.width;
                target.options.width = invertAxes ? options.target.line.width : targetSlot.width();
                target.reflow(targetSlot);
            }

            if (bullet.note) {
                bullet.note.reflow(box);
            }

            bullet.box = box;
        },

        createVisual: function () {
            ChartElement.fn.createVisual.call(this);

            var options = this.options;
            var body = draw.Path.fromRect(this.box.toRect(), {
                fill: {
                    color: options.color,
                    opacity: options.opacity
                },
                stroke: null
            });

            if (options.border.width > 0) {
                body.options.set("stroke", {
                    color: options.border.color || options.color,
                    width: options.border.width,
                    dashType: options.border.dashType,
                    opacity: valueOrDefault(options.border.opacity, options.opacity)
                });
            }

            alignPathToPixel(body);
            this.visual.append(body);
        },

        createAnimation: function () {
            var options = this.options;

            deepExtend(options, {
                animation: {
                    aboveAxis: this.aboveAxis,
                    vertical: options.vertical
                }
            });

            ChartElement.fn.createAnimation.call(this);
        },

        tooltipAnchor: function (tooltipWidth, tooltipHeight) {
            var bar = this,
                options = bar.options,
                box = bar.box,
                vertical = options.vertical,
                aboveAxis = bar.aboveAxis,
                x, y;

            if (vertical) {
                x = box.x2 + TOOLTIP_OFFSET;
                y = aboveAxis ? box.y1 : box.y2 - tooltipHeight;
            } else {
                if (options.isStacked) {
                    x = aboveAxis ? box.x2 - tooltipWidth : box.x1;
                    y = box.y1 - tooltipHeight - TOOLTIP_OFFSET;
                } else {
                    x = aboveAxis ? box.x2 + TOOLTIP_OFFSET : box.x1 - tooltipWidth - TOOLTIP_OFFSET;
                    y = box.y1;
                }
            }

            return new Point2D(x, y);
        },

        createHighlight: function (style) {
            return draw.Path.fromRect(this.box.toRect(), style);
        },

        formatValue: function (format) {
            var bullet = this;

            return bullet.owner.formatPointValue(bullet, format);
        }
    });
    deepExtend(Bullet.fn, PointEventsMixin);
    deepExtend(Bullet.fn, NoteMixin);

    var Target = ShapeElement.extend();
    deepExtend(Target.fn, PointEventsMixin);

    var ErrorBarBase = ChartElement.extend({
        init: function (low, high, isVertical, chart, series, options) {
            var errorBar = this;
            errorBar.low = low;
            errorBar.high = high;
            errorBar.isVertical = isVertical;
            errorBar.chart = chart;
            errorBar.series = series;

            ChartElement.fn.init.call(errorBar, options);
        },

        options: {
            animation: {
                type: FADEIN,
                delay: INITIAL_ANIMATION_DURATION
            },
            endCaps: true,
            line: {
                width: 1
            },
            zIndex: 1
        },

        getAxis: function () { },

        reflow: function (targetBox) {
            var linePoints,
                errorBar = this,
                endCaps = errorBar.options.endCaps,
                isVertical = errorBar.isVertical,
                axis = errorBar.getAxis(),
                valueBox = axis.getSlot(errorBar.low, errorBar.high),
                centerBox = targetBox.center(),
                capsWidth = errorBar.getCapsWidth(targetBox, isVertical),
                capValue = isVertical ? centerBox.x : centerBox.y,
                capStart = capValue - capsWidth,
                capEnd = capValue + capsWidth;

            if (isVertical) {
                linePoints = [
                    Point2D(centerBox.x, valueBox.y1),
                    Point2D(centerBox.x, valueBox.y2)
                ];
                if (endCaps) {
                    linePoints.push(Point2D(capStart, valueBox.y1),
                        Point2D(capEnd, valueBox.y1),
                        Point2D(capStart, valueBox.y2),
                        Point2D(capEnd, valueBox.y2));
                }
            } else {
                linePoints = [
                    Point2D(valueBox.x1, centerBox.y),
                    Point2D(valueBox.x2, centerBox.y)
                ];
                if (endCaps) {
                    linePoints.push(Point2D(valueBox.x1, capStart),
                        Point2D(valueBox.x1, capEnd),
                        Point2D(valueBox.x2, capStart),
                        Point2D(valueBox.x2, capEnd));
                }
            }

            errorBar.linePoints = linePoints;
        },

        getCapsWidth: function (box, isVertical) {
            var boxSize = isVertical ? box.width() : box.height(),
                capsWidth = math.min(math.floor(boxSize / 2), DEFAULT_ERROR_BAR_WIDTH) || DEFAULT_ERROR_BAR_WIDTH;

            return capsWidth;
        },

        createVisual: function () {
            var errorBar = this,
                options = errorBar.options,
                parent = errorBar.parent,
                lineOptions = {
                    stroke: {
                        color: options.color,
                        width: options.line.width,
                        dashType: options.line.dashType
                    }
                },
                linePoints = errorBar.linePoints;

            ChartElement.fn.createVisual.call(this);

            for (var idx = 0; idx < linePoints.length; idx += 2) {
                var line = new draw.Path(lineOptions)
                    .moveTo(linePoints[idx].x, linePoints[idx].y)
                    .lineTo(linePoints[idx + 1].x, linePoints[idx + 1].y);

                this.visual.append(line);
            }
        }
    });

    var CategoricalErrorBar = ErrorBarBase.extend({
        getAxis: function () {
            var errorBar = this,
                chart = errorBar.chart,
                series = errorBar.series,
                axis = chart.seriesValueAxis(series);

            return axis;
        }
    });

    var ScatterErrorBar = ErrorBarBase.extend({
        getAxis: function () {
            var errorBar = this,
                chart = errorBar.chart,
                series = errorBar.series,
                axes = chart.seriesAxes(series),
                axis = errorBar.isVertical ? axes.y : axes.x;
            return axis;
        }
    });

    var LinePoint = ChartElement.extend({
        init: function (value, options) {
            var point = this;

            ChartElement.fn.init.call(point);

            point.value = value;
            point.options = options;
            point.aboveAxis = valueOrDefault(point.options.aboveAxis, true);
            point.tooltipTracking = true;
        },

        defaults: {
            vertical: true,
            markers: {
                visible: true,
                background: WHITE,
                size: LINE_MARKER_SIZE,
                type: CIRCLE,
                border: {
                    width: 2
                },
                opacity: 1
            },
            labels: {
                visible: false,
                position: ABOVE,
                margin: getSpacing(3),
                padding: getSpacing(4),
                animation: {
                    type: FADEIN,
                    delay: INITIAL_ANIMATION_DURATION
                }
            },
            notes: {
                label: {}
            },
            highlight: {
                markers: {
                    border: {}
                }
            }
        },

        render: function () {
            var point = this,
                options = point.options,
                markers = options.markers,
                labels = options.labels,
                labelText = point.value;

            if (point._rendered) {
                return;
            } else {
                point._rendered = true;
            }

            if (markers.visible && markers.size) {
                point.marker = point.createMarker();
                point.append(point.marker);
            }

            if (labels.visible) {
                if (labels.template) {
                    var labelTemplate = template(labels.template);
                    labelText = labelTemplate({
                        dataItem: point.dataItem,
                        category: point.category,
                        value: point.value,
                        percentage: point.percentage,
                        series: point.series
                    });
                } else if (labels.format) {
                    labelText = point.formatValue(labels.format);
                }
                point.label = new TextBox(labelText,
                    deepExtend({
                        align: CENTER,
                        vAlign: CENTER,
                        margin: {
                            left: 5,
                            right: 5
                        },
                        zIndex: this.series.zIndex
                    }, labels)
                );
                point.append(point.label);
            }

            point.createNote();

            if (point.errorBar) {
                point.append(point.errorBar);
            }
        },

        markerBorder: function () {
            var options = this.options.markers;
            var background = options.background;
            var border = deepExtend({ color: this.color }, options.border);

            if (!defined(border.color)) {
                border.color =
                    new Color(background).brightness(BAR_BORDER_BRIGHTNESS).toHex();
            }

            return border;
        },

        createVisual: noop,

        createMarker: function () {
            var options = this.options.markers;
            var marker = new ShapeElement({
                type: options.type,
                width: options.size,
                height: options.size,
                rotation: options.rotation,
                background: options.background,
                border: this.markerBorder(),
                opacity: options.opacity,
                zIndex: this.series.zIndex,
                animation: options.animation
            });

            return marker;
        },

        markerBox: function () {
            if (!this.marker) {
                this.marker = this.createMarker();
                this.marker.reflow(this._childBox);
            }

            return this.marker.box;
        },

        reflow: function (targetBox) {
            var point = this,
                options = point.options,
                vertical = options.vertical,
                aboveAxis = point.aboveAxis,
                childBox, center;

            point.render();

            point.box = targetBox;
            childBox = targetBox.clone();

            if (vertical) {
                if (aboveAxis) {
                    childBox.y1 -= childBox.height();
                } else {
                    childBox.y2 += childBox.height();
                }
            } else {
                if (aboveAxis) {
                    childBox.x1 += childBox.width();
                } else {
                    childBox.x2 -= childBox.width();
                }
            }

            point._childBox = childBox;
            if (point.marker) {
                point.marker.reflow(childBox);
            }

            point.reflowLabel(childBox);

            if (point.errorBars) {
                for (var i = 0; i < point.errorBars.length; i++) {
                    point.errorBars[i].reflow(childBox);
                }
            }

            if (point.note) {
                var noteTargetBox = point.markerBox();

                if (!point.marker) {
                    center = noteTargetBox.center();
                    noteTargetBox = Box2D(center.x, center.y, center.x, center.y);
                }

                point.note.reflow(noteTargetBox);
            }
        },

        reflowLabel: function (box) {
            var point = this,
                options = point.options,
                label = point.label,
                anchor = options.labels.position;

            if (label) {
                anchor = anchor === ABOVE ? TOP : anchor;
                anchor = anchor === BELOW ? BOTTOM : anchor;

                label.reflow(box);
                label.box.alignTo(point.markerBox(), anchor);
                label.reflow(label.box);
            }
        },

        createHighlight: function () {
            var highlight = this.options.highlight;
            var markers = highlight.markers;
            var defaultColor = this.markerBorder().color;
            var options = this.options.markers;

            var shadow = new ShapeElement({
                type: options.type,
                width: options.size,
                height: options.size,
                rotation: options.rotation,
                background: markers.color || defaultColor,
                border: {
                    color: markers.border.color,
                    width: markers.border.width,
                    opacity: markers.border.opacity || 0
                },
                fillOpacity: markers.opacity || 1
            });
            shadow.reflow(this._childBox);

            return shadow.getElement();
        },

        tooltipAnchor: function (tooltipWidth, tooltipHeight) {
            var point = this,
                markerBox = point.markerBox(),
                options = point.options,
                aboveAxis = point.aboveAxis,
                x = markerBox.x2 + TOOLTIP_OFFSET,
                y = aboveAxis ? markerBox.y1 - tooltipHeight : markerBox.y2,
                clipBox = point.owner.pane.clipBox(),
                showTooltip = !clipBox || clipBox.overlaps(markerBox);

            if (showTooltip) {
                return Point2D(x, y);
            }
        },

        formatValue: function (format) {
            var point = this;

            return point.owner.formatPointValue(point, format);
        }
    });
    deepExtend(LinePoint.fn, PointEventsMixin);
    deepExtend(LinePoint.fn, NoteMixin);

    var Bubble = LinePoint.extend({
        init: function (value, options) {
            var point = this;

            LinePoint.fn.init.call(point, value, options);

            point.category = value.category;
        },

        defaults: {
            labels: {
                position: CENTER
            },
            highlight: {
                opacity: 1,
                border: {
                    width: 1,
                    opacity: 1
                }
            }
        },

        createHighlight: function () {
            var highlight = this.options.highlight;
            var border = highlight.border;
            var markers = this.options.markers;
            var center = this.box.center();
            var radius = markers.size / 2 - border.width / 2;
            var overlay = new draw.Circle(new geom.Circle([center.x, center.y], radius), {
                stroke: {
                    color: border.color ||
                        new Color(markers.background).brightness(BAR_BORDER_BRIGHTNESS).toHex(),
                    width: border.width,
                    opacity: border.opacity
                },
                fill: {
                    color: markers.background,
                    opacity: highlight.opacity
                }
            });

            return overlay;
        }
    });

    var LineSegment = ChartElement.extend({
        init: function (linePoints, series, seriesIx) {
            var segment = this;

            ChartElement.fn.init.call(segment);

            segment.linePoints = linePoints;
            segment.series = series;
            segment.seriesIx = seriesIx;
        },

        options: {
            closed: false
        },

        points: function (visualPoints) {
            var segment = this,
                linePoints = segment.linePoints.concat(visualPoints || []),
                points = [];

            for (var i = 0, length = linePoints.length; i < length; i++) {
                if (linePoints[i].visible !== false) {
                    points.push(linePoints[i]._childBox.toRect().center());
                }
            }

            return points;
        },

        createVisual: function () {
            var options = this.options;
            var series = this.series;
            var defaults = series._defaults;
            var color = series.color;

            if (isFn(color) && defaults) {
                color = defaults.color;
            }

            var line = draw.Path.fromPoints(this.points(), {
                stroke: {
                    color: color,
                    width: series.width,
                    opacity: series.opacity,
                    dashType: series.dashType
                },
                zIndex: series.zIndex
            });

            if (options.closed) {
                line.close();
            }

            this.visual = line;
        },

        aliasFor: function (e, coords) {
            var segment = this,
                seriesIx = segment.seriesIx;

            return segment.parent.getNearestPoint(coords.x, coords.y, seriesIx);
        }
    });

    var LineChartMixin = {
        renderSegments: function () {
            var chart = this,
                options = chart.options,
                series = options.series,
                seriesPoints = chart.seriesPoints,
                currentSeries, seriesIx,
                seriesCount = seriesPoints.length,
                sortedPoints, linePoints,
                point, pointIx, pointCount,
                lastSegment;

            this._segments = [];

            for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
                currentSeries = series[seriesIx];
                sortedPoints = chart.sortPoints(seriesPoints[seriesIx]);
                pointCount = sortedPoints.length;
                linePoints = [];

                for (pointIx = 0; pointIx < pointCount; pointIx++) {
                    point = sortedPoints[pointIx];
                    if (point) {
                        linePoints.push(point);
                    } else if (chart.seriesMissingValues(currentSeries) !== INTERPOLATE) {
                        if (linePoints.length > 1) {
                            lastSegment = chart.createSegment(
                                linePoints, currentSeries, seriesIx, lastSegment
                            );
                            this._addSegment(lastSegment);
                        }
                        linePoints = [];
                    }
                }

                if (linePoints.length > 1) {
                    lastSegment = chart.createSegment(
                        linePoints, currentSeries, seriesIx, lastSegment
                    );
                    this._addSegment(lastSegment);
                }
            }
        },

        _addSegment: function (segment) {
            this._segments.push(segment);
            this.children.unshift(segment);
            segment.parent = this;
        },

        sortPoints: function (points) {
            return points;
        },

        seriesMissingValues: function (series) {
            var missingValues = series.missingValues,
                assumeZero = !missingValues && this.options.isStacked;

            return assumeZero ? ZERO : missingValues || INTERPOLATE;
        },

        getNearestPoint: function (x, y, seriesIx) {
            var target = new Point2D(x, y);
            var allPoints = this.seriesPoints[seriesIx];
            var nearestPointDistance = MAX_VALUE;
            var nearestPoint;

            for (var i = 0; i < allPoints.length; i++) {
                var point = allPoints[i];

                if (point && defined(point.value) && point.value !== null && point.visible !== false) {
                    var pointBox = point.box;
                    var pointDistance = pointBox.center().distanceTo(target);

                    if (pointDistance < nearestPointDistance) {
                        nearestPoint = point;
                        nearestPointDistance = pointDistance;
                    }
                }
            }

            return nearestPoint;
        }
    };

    var LineChart = CategoricalChart.extend({
        render: function () {
            var chart = this;

            CategoricalChart.fn.render.apply(chart);

            chart.updateStackRange();
            chart.renderSegments();
        },

        pointType: function () {
            return LinePoint;
        },

        createPoint: function (data, fields) {
            var chart = this;
            var categoryIx = fields.categoryIx;
            var category = fields.category;
            var series = fields.series;
            var seriesIx = fields.seriesIx;
            var value = data.valueFields.value;
            var options = chart.options;
            var isStacked = options.isStacked;
            var categoryPoints = chart.categoryPoints[categoryIx];
            var missingValues = chart.seriesMissingValues(series);
            var stackPoint;
            var plotValue = 0;
            var point;
            var pointOptions;

            if (!defined(value) || value === null) {
                if (missingValues === ZERO) {
                    value = 0;
                } else {
                    return null;
                }
            }

            pointOptions = this.pointOptions(series, seriesIx);
            pointOptions = chart.evalPointOptions(
                pointOptions, value, category, categoryIx, series, seriesIx
            );

            var color = data.fields.color || series.color;
            if (kendo.isFunction(series.color)) {
                color = pointOptions.color;
            }

            point = new LinePoint(value, pointOptions);
            point.color = color;

            chart.append(point);

            return point;
        },

        plotRange: function (point) {
            var plotValue = this.plotValue(point);

            if (this.options.isStacked) {
                var categoryIx = point.categoryIx;
                var categoryPts = this.categoryPoints[categoryIx];

                for (var i = 0; i < categoryPts.length; i++) {
                    var other = categoryPts[i];

                    if (point === other) {
                        break;
                    }

                    plotValue += this.plotValue(other);
                }

            }

            return [plotValue, plotValue];
        },

        createSegment: function (linePoints, currentSeries, seriesIx) {
            var pointType,
                style = currentSeries.style;

            if (style === STEP) {
                pointType = StepLineSegment;
            } else if (style === SMOOTH) {
                pointType = SplineSegment;
            } else {
                pointType = LineSegment;
            }

            return new pointType(linePoints, currentSeries, seriesIx);
        }
    });
    deepExtend(LineChart.fn, LineChartMixin);

    var StepLineSegment = LineSegment.extend({
        points: function (visualPoints) {
            var segment = this,
                points;

            points = segment.calculateStepPoints(segment.linePoints);

            if (visualPoints && visualPoints.length) {
                points = points.concat(segment.calculateStepPoints(visualPoints).reverse());
            }

            return points;
        },

        calculateStepPoints: function (points) {
            var segment = this,
                chart = segment.parent,
                plotArea = chart.plotArea,
                categoryAxis = plotArea.seriesCategoryAxis(segment.series),
                isInterpolate = chart.seriesMissingValues(segment.series) === INTERPOLATE,
                length = points.length,
                reverse = categoryAxis.options.reverse,
                vertical = categoryAxis.options.vertical,
                dir = reverse ? 2 : 1,
                revDir = reverse ? 1 : 2,
                prevPoint, point, i,
                prevMarkerBoxCenter, markerBoxCenter,
                result = [];

            for (i = 1; i < length; i++) {
                prevPoint = points[i - 1];
                point = points[i];
                prevMarkerBoxCenter = prevPoint.markerBox().center();
                markerBoxCenter = point.markerBox().center();
                if (categoryAxis.options.justified) {
                    result.push(new geom.Point(prevMarkerBoxCenter.x, prevMarkerBoxCenter.y));
                    if (vertical) {
                        result.push(new geom.Point(prevMarkerBoxCenter.x, markerBoxCenter.y));
                    } else {
                        result.push(new geom.Point(markerBoxCenter.x, prevMarkerBoxCenter.y));
                    }
                    result.push(new geom.Point(markerBoxCenter.x, markerBoxCenter.y));
                } else {
                    if (vertical) {
                        result.push(new geom.Point(prevMarkerBoxCenter.x, prevPoint.box[Y + dir]));
                        result.push(new geom.Point(prevMarkerBoxCenter.x, prevPoint.box[Y + revDir]));
                        if (isInterpolate) {
                            result.push(new geom.Point(prevMarkerBoxCenter.x, point.box[Y + dir]));
                        }
                        result.push(new geom.Point(markerBoxCenter.x, point.box[Y + dir]));
                        result.push(new geom.Point(markerBoxCenter.x, point.box[Y + revDir]));
                    } else {
                        result.push(new geom.Point(prevPoint.box[X + dir], prevMarkerBoxCenter.y));
                        result.push(new geom.Point(prevPoint.box[X + revDir], prevMarkerBoxCenter.y));
                        if (isInterpolate) {
                            result.push(new geom.Point(point.box[X + dir], prevMarkerBoxCenter.y));
                        }
                        result.push(new geom.Point(point.box[X + dir], markerBoxCenter.y));
                        result.push(new geom.Point(point.box[X + revDir], markerBoxCenter.y));
                    }
                }
            }

            return result || [];
        }
    });

    var SplineSegment = LineSegment.extend({
        createVisual: function () {
            var options = this.options;
            var series = this.series;
            var defaults = series._defaults;
            var color = series.color;

            if (isFn(color) && defaults) {
                color = defaults.color;
            }

            var curveProcessor = new CurveProcessor(this.options.closed);
            var segments = curveProcessor.process(this.points());
            var curve = new draw.Path({
                stroke: {
                    color: color,
                    width: series.width,
                    opacity: series.opacity,
                    dashType: series.dashType
                },
                zIndex: series.zIndex
            });

            curve.segments.push.apply(curve.segments, segments);

            this.visual = curve;
        }
    });

    var AreaSegmentMixin = {
        points: function () {
            var segment = this,
                chart = segment.parent,
                plotArea = chart.plotArea,
                invertAxes = chart.options.invertAxes,
                valueAxis = chart.seriesValueAxis(segment.series),
                valueAxisLineBox = valueAxis.lineBox(),
                categoryAxis = plotArea.seriesCategoryAxis(segment.series),
                categoryAxisLineBox = categoryAxis.lineBox(),
                end = invertAxes ? categoryAxisLineBox.x1 : categoryAxisLineBox.y1,
                stackPoints = segment.stackPoints,
                points = segment._linePoints(stackPoints),
                pos = invertAxes ? X : Y,
                firstPoint, lastPoint;

            end = limitValue(end, valueAxisLineBox[pos + 1], valueAxisLineBox[pos + 2]);
            if (!segment.stackPoints && points.length > 1) {
                firstPoint = points[0];
                lastPoint = last(points);

                if (invertAxes) {
                    points.unshift(new geom.Point(end, firstPoint.y));
                    points.push(new geom.Point(end, lastPoint.y));
                } else {
                    points.unshift(new geom.Point(firstPoint.x, end));
                    points.push(new geom.Point(lastPoint.x, end));
                }
            }

            return points;
        },

        createVisual: function () {
            var options = this.options;
            var series = this.series;
            var defaults = series._defaults;
            var color = series.color;

            if (isFn(color) && defaults) {
                color = defaults.color;
            }

            this.visual = new draw.Group({
                zIndex: series.zIndex
            });

            this.createArea(color);
            this.createLine(color);
        },

        createLine: function (color) {
            var series = this.series;
            var lineOptions = deepExtend({
                color: color,
                opacity: series.opacity
            }, series.line
                );

            if (lineOptions.visible !== false && lineOptions.width > 0) {
                var line = draw.Path.fromPoints(this._linePoints(), {
                    stroke: {
                        color: lineOptions.color,
                        width: lineOptions.width,
                        opacity: lineOptions.opacity,
                        dashType: lineOptions.dashType,
                        lineCap: "butt"
                    }
                });

                this.visual.append(line);
            }
        },

        createArea: function (color) {
            var series = this.series;

            var area = draw.Path.fromPoints(this.points(), {
                fill: {
                    color: color,
                    opacity: series.opacity
                },
                stroke: null
            });

            this.visual.append(area);
        }
    };

    var AreaSegment = LineSegment.extend({
        init: function (linePoints, stackPoints, currentSeries, seriesIx) {
            var segment = this;

            segment.stackPoints = stackPoints;

            LineSegment.fn.init.call(segment, linePoints, currentSeries, seriesIx);
        },

        _linePoints: LineSegment.fn.points
    });
    deepExtend(AreaSegment.fn, AreaSegmentMixin);

    var AreaChart = LineChart.extend({
        createSegment: function (linePoints, currentSeries, seriesIx, prevSegment) {
            var chart = this,
                options = chart.options,
                isStacked = options.isStacked,
                stackPoints, pointType,
                style = (currentSeries.line || {}).style;

            if (isStacked && seriesIx > 0 && prevSegment) {
                stackPoints = prevSegment.linePoints;
                if (style !== STEP) {
                    stackPoints = stackPoints.slice(0).reverse();
                }
            }

            if (style === SMOOTH) {
                return new SplineAreaSegment(linePoints, prevSegment, isStacked, currentSeries, seriesIx);
            }

            if (style === STEP) {
                pointType = StepAreaSegment;
            } else {
                pointType = AreaSegment;
            }

            return new pointType(linePoints, stackPoints, currentSeries, seriesIx);
        },

        seriesMissingValues: function (series) {
            return series.missingValues || ZERO;
        }
    });

    var SplineAreaSegment = AreaSegment.extend({
        init: function (linePoints, prevSegment, isStacked, currentSeries, seriesIx) {
            var segment = this;

            segment.prevSegment = prevSegment;
            segment.isStacked = isStacked;
            LineSegment.fn.init.call(segment, linePoints, currentSeries, seriesIx);
        },

        strokeSegments: function () {
            var segments = this._strokeSegments;

            if (!segments) {
                var curveProcessor = new CurveProcessor(this.options.closed);
                var linePoints = LineSegment.fn.points.call(this);
                segments = this._strokeSegments = curveProcessor.process(linePoints);
            }

            return segments;
        },

        createVisual: function () {
            var options = this.options;
            var series = this.series;
            var defaults = series._defaults;
            var color = series.color;

            if (isFn(color) && defaults) {
                color = defaults.color;
            }

            this.visual = new draw.Group();

            this.createFill({
                fill: {
                    color: color,
                    opacity: series.opacity
                },
                stroke: null
            });

            this.createStroke({
                stroke: deepExtend({
                    color: color,
                    opacity: series.opacity,
                    lineCap: "butt"
                }, series.line)
            });
        },

        createFill: function (style) {
            var strokeSegments = this.strokeSegments();
            var fillSegments = strokeSegments.slice(0);
            var prevSegment = this.prevSegment;

            if (this.isStacked && prevSegment) {
                var prevStrokeSegments = prevSegment.strokeSegments();
                var prevAnchor = last(prevStrokeSegments).anchor();

                fillSegments.push(new draw.Segment(
                    prevAnchor,
                    prevAnchor,
                    last(strokeSegments).anchor()
                ));

                var stackSegments = $.map(prevStrokeSegments, function (segment) {
                    return new draw.Segment(
                        segment.anchor(),
                        segment.controlOut(),
                        segment.controlIn()
                    );
                }).reverse();

                append(fillSegments, stackSegments);

                var firstAnchor = fillSegments[0].anchor();
                fillSegments.push(new draw.Segment(
                    firstAnchor,
                    firstAnchor,
                    last(stackSegments).anchor()
                ));
            }

            var fill = new draw.Path(style);
            fill.segments.push.apply(fill.segments, fillSegments);
            this.closeFill(fill);

            this.visual.append(fill);
        },

        closeFill: function (fillPath) {
            var segment = this,
                chart = segment.parent,
                prevSegment = segment.prevSegment,
                plotArea = chart.plotArea,
                invertAxes = chart.options.invertAxes,
                valueAxis = chart.seriesValueAxis(segment.series),
                valueAxisLineBox = valueAxis.lineBox(),
                categoryAxis = plotArea.seriesCategoryAxis(segment.series),
                categoryAxisLineBox = categoryAxis.lineBox(),
                end = invertAxes ? categoryAxisLineBox.x1 : categoryAxisLineBox.y1,
                pos = invertAxes ? X : Y,
                segments = segment.strokeSegments(),
                firstPoint = segments[0].anchor(),
                lastPoint = last(segments).anchor();

            end = limitValue(end, valueAxisLineBox[pos + 1], valueAxisLineBox[pos + 2]);
            if (!(chart.options.isStacked && prevSegment) && segments.length > 1) {
                if (invertAxes) {
                    fillPath.lineTo(end, lastPoint.y)
                            .lineTo(end, firstPoint.y);
                } else {
                    fillPath.lineTo(lastPoint.x, end)
                            .lineTo(firstPoint.x, end);
                }
            }
        },

        createStroke: function (style) {
            if (style.stroke.width > 0) {
                var stroke = new draw.Path(style);
                stroke.segments.push.apply(stroke.segments, this.strokeSegments());

                this.visual.append(stroke);
            }
        }
    });

    var StepAreaSegment = StepLineSegment.extend({
        init: function (linePoints, stackPoints, currentSeries, seriesIx) {
            var segment = this;

            segment.stackPoints = stackPoints;

            StepLineSegment.fn.init.call(segment, linePoints, currentSeries, seriesIx);
        },

        _linePoints: StepLineSegment.fn.points
    });
    deepExtend(StepAreaSegment.fn, AreaSegmentMixin);

    var ScatterChart = ChartElement.extend({
        init: function (plotArea, options) {
            var chart = this;

            ChartElement.fn.init.call(chart, options);

            chart.plotArea = plotArea;

            // X and Y axis ranges grouped by name, e.g.:
            // primary: { min: 0, max: 1 }
            chart.xAxisRanges = {};
            chart.yAxisRanges = {};

            chart.points = [];
            chart.seriesPoints = [];
            chart.seriesOptions = [];
            chart._evalSeries = [];

            chart.render();
        },

        options: {
            series: [],
            tooltip: {
                format: "{0}, {1}"
            },
            labels: {
                format: "{0}, {1}"
            },
            clip: true
        },

        render: function () {
            var chart = this;

            chart.traverseDataPoints(proxy(chart.addValue, chart));
        },

        addErrorBar: function (point, field, fields) {
            var errorRange,
                chart = this,
                value = point.value[field],
                valueErrorField = field + "Value",
                lowField = field + "ErrorLow",
                highField = field + "ErrorHigh",
                seriesIx = fields.seriesIx,
                series = fields.series,
                errorBars = point.options.errorBars,
                lowValue = fields[lowField],
                highValue = fields[highField];

            if (isNumber(value)) {
                if (isNumber(lowValue) && isNumber(highValue)) {
                    errorRange = { low: lowValue, high: highValue };
                }

                if (errorBars && defined(errorBars[valueErrorField])) {
                    chart.seriesErrorRanges = chart.seriesErrorRanges || { x: [], y: [] };
                    chart.seriesErrorRanges[field][seriesIx] = chart.seriesErrorRanges[field][seriesIx] ||
                        new ErrorRangeCalculator(errorBars[valueErrorField], series, field);

                    errorRange = chart.seriesErrorRanges[field][seriesIx].getErrorRange(value);
                }

                if (errorRange) {
                    chart.addPointErrorBar(errorRange, point, field);
                }
            }
        },

        addPointErrorBar: function (errorRange, point, field) {
            var chart = this,
                low = errorRange.low,
                high = errorRange.high,
                series = point.series,
                isVertical = field === Y,
                options = point.options.errorBars,
                item = {},
                errorBar;

            point[field + "Low"] = low;
            point[field + "High"] = high;

            point.errorBars = point.errorBars || [];
            errorBar = new ScatterErrorBar(low, high, isVertical, chart, series, options);
            point.errorBars.push(errorBar);
            point.append(errorBar);

            item[field] = low;
            chart.updateRange(item, series);
            item[field] = high;
            chart.updateRange(item, series);
        },

        addValue: function (value, fields) {
            var chart = this,
                point,
                x = value.x,
                y = value.y,
                seriesIx = fields.seriesIx,
                seriesPoints = chart.seriesPoints[seriesIx];

            chart.updateRange(value, fields.series);

            if (defined(x) && x !== null && defined(y) && y !== null) {
                point = chart.createPoint(value, fields);
                if (point) {
                    extend(point, fields);
                    chart.addErrorBar(point, X, fields);
                    chart.addErrorBar(point, Y, fields);
                }
            }

            chart.points.push(point);
            seriesPoints.push(point);
        },

        updateRange: function (value, series) {
            var chart = this,
                x = value.x,
                y = value.y,
                xAxisName = series.xAxis,
                yAxisName = series.yAxis,
                xAxisRange = chart.xAxisRanges[xAxisName],
                yAxisRange = chart.yAxisRanges[yAxisName];

            if (defined(x) && x !== null) {
                xAxisRange = chart.xAxisRanges[xAxisName] =
                    xAxisRange || { min: MAX_VALUE, max: MIN_VALUE };

                if (typeof (x) === STRING) {
                    x = toDate(x);
                }

                xAxisRange.min = math.min(xAxisRange.min, x);
                xAxisRange.max = math.max(xAxisRange.max, x);
            }

            if (defined(y) && y !== null) {
                yAxisRange = chart.yAxisRanges[yAxisName] =
                    yAxisRange || { min: MAX_VALUE, max: MIN_VALUE };

                if (typeof (y) === STRING) {
                    y = toDate(y);
                }

                yAxisRange.min = math.min(yAxisRange.min, y);
                yAxisRange.max = math.max(yAxisRange.max, y);
            }
        },

        evalPointOptions: function (options, value, fields) {
            var series = fields.series;
            var seriesIx = fields.seriesIx;
            var state = { defaults: series._defaults, excluded: ["data", "tooltip", "tempate"] };

            var doEval = this._evalSeries[seriesIx];
            if (!defined(doEval)) {
                this._evalSeries[seriesIx] = doEval = evalOptions(options, {}, state, true);
            }

            if (doEval) {
                options = deepExtend({}, options);
                evalOptions(options, {
                    value: value,
                    series: series,
                    dataItem: fields.dataItem
                }, state);
            }

            return options;
        },

        pointType: function () {
            return LinePoint;
        },

        pointOptions: function (series, seriesIx) {
            var options = this.seriesOptions[seriesIx];
            if (!options) {
                var defaults = this.pointType().fn.defaults;
                this.seriesOptions[seriesIx] = options = deepExtend({}, defaults, {
                    markers: {
                        opacity: series.opacity
                    },
                    tooltip: {
                        format: this.options.tooltip.format
                    },
                    labels: {
                        format: this.options.labels.format
                    }
                }, series);
            }

            return options;
        },

        createPoint: function (value, fields) {
            var chart = this,
                series = fields.series,
                point;

            var pointOptions = this.pointOptions(series, fields.seriesIx);
            var color = fields.color || series.color;

            pointOptions = chart.evalPointOptions(pointOptions, value, fields);

            if (kendo.isFunction(series.color)) {
                color = pointOptions.color;
            }

            point = new LinePoint(value, pointOptions);
            point.color = color;

            chart.append(point);

            return point;
        },

        seriesAxes: function (series) {
            var plotArea = this.plotArea,
                xAxisName = series.xAxis,
                xAxis = xAxisName ?
                        plotArea.namedXAxes[xAxisName] :
                        plotArea.axisX,
                yAxisName = series.yAxis,
                yAxis = yAxisName ?
                        plotArea.namedYAxes[yAxisName] :
                        plotArea.axisY;

            if (!xAxis) {
                throw new Error("Unable to locate X axis with name " + xAxisName);
            }

            if (!yAxis) {
                throw new Error("Unable to locate Y axis with name " + yAxisName);
            }

            return {
                x: xAxis,
                y: yAxis
            };
        },

        reflow: function (targetBox) {
            var chart = this,
                chartPoints = chart.points,
                pointIx = 0,
                point,
                seriesAxes,
                clip = chart.options.clip,
                limit = !chart.options.clip;

            chart.traverseDataPoints(function (value, fields) {
                point = chartPoints[pointIx++];
                seriesAxes = chart.seriesAxes(fields.series);

                var slotX = seriesAxes.x.getSlot(value.x, value.x, limit),
                    slotY = seriesAxes.y.getSlot(value.y, value.y, limit),
                    pointSlot;

                if (point) {
                    if (slotX && slotY) {
                        pointSlot = chart.pointSlot(slotX, slotY);
                        point.reflow(pointSlot);
                    } else {
                        point.visible = false;
                    }
                }
            });

            chart.box = targetBox;
        },

        pointSlot: function (slotX, slotY) {
            return new Box2D(slotX.x1, slotY.y1, slotX.x2, slotY.y2);
        },

        traverseDataPoints: function (callback) {
            var chart = this,
                options = chart.options,
                series = options.series,
                seriesPoints = chart.seriesPoints,
                pointIx,
                seriesIx,
                currentSeries,
                currentSeriesPoints,
                pointData,
                value,
                fields;

            for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
                currentSeries = series[seriesIx];
                currentSeriesPoints = seriesPoints[seriesIx];
                if (!currentSeriesPoints) {
                    seriesPoints[seriesIx] = [];
                }

                for (pointIx = 0; pointIx < currentSeries.data.length; pointIx++) {
                    pointData = this._bindPoint(currentSeries, seriesIx, pointIx);
                    value = pointData.valueFields;
                    fields = pointData.fields;

                    callback(value, deepExtend({
                        pointIx: pointIx,
                        series: currentSeries,
                        seriesIx: seriesIx,
                        dataItem: currentSeries.data[pointIx],
                        owner: chart
                    }, fields));
                }
            }
        },

        _bindPoint: CategoricalChart.fn._bindPoint,

        formatPointValue: function (point, format) {
            var value = point.value;
            return autoFormat(format, value.x, value.y);
        },

        createVisual: noop
    });

    var ScatterLineChart = ScatterChart.extend({
        render: function () {
            var chart = this;

            ScatterChart.fn.render.call(chart);

            chart.renderSegments();
        },

        createSegment: function (linePoints, currentSeries, seriesIx) {
            var pointType,
                style = currentSeries.style;

            if (style === SMOOTH) {
                pointType = SplineSegment;
            } else {
                pointType = LineSegment;
            }

            return new pointType(linePoints, currentSeries, seriesIx);
        }
    });
    deepExtend(ScatterLineChart.fn, LineChartMixin);

    var BubbleChart = ScatterChart.extend({
        options: {
            tooltip: {
                format: "{3}"
            },
            labels: {
                format: "{3}"
            }
        },

        addValue: function (value, fields) {
            if ((value.size !== null && value.size >= 0) || fields.series.negativeValues.visible) {
                ScatterChart.fn.addValue.call(this, value, fields);
            }
        },

        reflow: function (box) {
            var chart = this;

            chart.updateBubblesSize(box);
            ScatterChart.fn.reflow.call(chart, box);
        },

        pointType: function () {
            return Bubble;
        },

        createPoint: function (value, fields) {
            var chart = this,
                series = fields.series,
                seriesColors = chart.plotArea.options.seriesColors || [],
                pointsCount = series.data.length,
                delay = fields.pointIx * (INITIAL_ANIMATION_DURATION / pointsCount),
                animationOptions = {
                    delay: delay,
                    duration: INITIAL_ANIMATION_DURATION - delay,
                    type: BUBBLE
                },
                point, pointOptions;

            var color = fields.color || series.color;
            if (value.size < 0 && series.negativeValues.visible) {
                color = valueOrDefault(
                    series.negativeValues.color, color
                );
            }

            pointOptions = deepExtend({
                labels: {
                    animation: {
                        delay: delay,
                        duration: INITIAL_ANIMATION_DURATION - delay
                    }
                }
            }, this.pointOptions(series, fields.seriesIx), {
                markers: {
                    type: CIRCLE,
                    border: series.border,
                    opacity: series.opacity,
                    animation: animationOptions
                }
            });

            pointOptions = chart.evalPointOptions(pointOptions, value, fields);
            if (kendo.isFunction(series.color)) {
                color = pointOptions.color;
            }

            pointOptions.markers.background = color;

            point = new Bubble(value, pointOptions);
            point.color = color;

            chart.append(point);

            return point;
        },

        updateBubblesSize: function (box) {
            var chart = this,
                options = chart.options,
                series = options.series,
                boxSize = math.min(box.width(), box.height()),
                seriesIx,
                pointIx;

            for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
                var currentSeries = series[seriesIx],
                    seriesPoints = chart.seriesPoints[seriesIx],
                    seriesMaxSize = chart.maxSize(seriesPoints),
                    minSize = currentSeries.minSize || math.max(boxSize * 0.02, 10),
                    maxSize = currentSeries.maxSize || boxSize * 0.2,
                    minR = minSize / 2,
                    maxR = maxSize / 2,
                    minArea = math.PI * minR * minR,
                    maxArea = math.PI * maxR * maxR,
                    areaRange = maxArea - minArea,
                    areaRatio = areaRange / seriesMaxSize;

                for (pointIx = 0; pointIx < seriesPoints.length; pointIx++) {
                    var point = seriesPoints[pointIx],
                        area = math.abs(point.value.size) * areaRatio,
                        r = math.sqrt((minArea + area) / math.PI);

                    deepExtend(point.options, {
                        markers: {
                            size: r * 2,
                            zIndex: maxR - r
                        },
                        labels: {
                            zIndex: maxR - r + 1
                        }
                    });
                }
            }
        },

        maxSize: function (seriesPoints) {
            var length = seriesPoints.length,
                max = 0,
                i,
                size;

            for (i = 0; i < length; i++) {
                size = seriesPoints[i].value.size;
                max = math.max(max, math.abs(size));
            }

            return max;
        },

        formatPointValue: function (point, format) {
            var value = point.value;
            return autoFormat(format, value.x, value.y, value.size, point.category);
        }
    });

    var Candlestick = ChartElement.extend({
        init: function (value, options) {
            ChartElement.fn.init.call(this, options);
            this.value = value;
        },

        options: {
            border: {
                _brightness: 0.8
            },
            line: {
                width: 2
            },
            overlay: {
                gradient: GLASS
            },
            tooltip: {
                format: "<table style='text-align: left;'>" +
                        "<th colspan='2'>{4:d}</th>" +
                        "<tr><td>Open:</td><td>{0:C}</td></tr>" +
                        "<tr><td>High:</td><td>{1:C}</td></tr>" +
                        "<tr><td>Low:</td><td>{2:C}</td></tr>" +
                        "<tr><td>Close:</td><td>{3:C}</td></tr>" +
                        "</table>"
            },
            highlight: {
                opacity: 1,
                border: {
                    width: 1,
                    opacity: 1
                },
                line: {
                    width: 1,
                    opacity: 1
                }
            },
            notes: {
                visible: true,
                label: {}
            }
        },

        reflow: function (box) {
            var point = this,
                options = point.options,
                chart = point.owner,
                value = point.value,
                valueAxis = chart.seriesValueAxis(options),
                points = [], mid, ocSlot, lhSlot;

            ocSlot = valueAxis.getSlot(value.open, value.close);
            lhSlot = valueAxis.getSlot(value.low, value.high);

            ocSlot.x1 = lhSlot.x1 = box.x1;
            ocSlot.x2 = lhSlot.x2 = box.x2;

            point.realBody = ocSlot;

            mid = lhSlot.center().x;
            points.push([[mid, lhSlot.y1], [mid, ocSlot.y1]]);
            points.push([[mid, ocSlot.y2], [mid, lhSlot.y2]]);

            point.lines = points;

            point.box = lhSlot.clone().wrap(ocSlot);

            if (!point._rendered) {
                point._rendered = true;
                point.createNote();
            }

            point.reflowNote();
        },

        reflowNote: function () {
            var point = this;

            if (point.note) {
                point.note.reflow(point.box);
            }
        },

        createVisual: function () {
            ChartElement.fn.createVisual.call(this);

            this.visual.append(
                this.mainVisual(this.options)
            );

            this.createOverlay();
        },

        mainVisual: function (options) {
            var group = new draw.Group();

            this.createBody(group, options);
            this.createLines(group, options);

            return group;
        },

        createBody: function (container, options) {
            var body = draw.Path.fromRect(this.realBody.toRect(), {
                fill: {
                    color: this.color,
                    opacity: options.opacity
                },
                stroke: null
            });

            if (options.border.width > 0) {
                body.options.set("stroke", {
                    color: this.getBorderColor(),
                    width: options.border.width,
                    dashType: options.border.dashType,
                    opacity: valueOrDefault(options.border.opacity, options.opacity)
                });
            }

            alignPathToPixel(body);
            container.append(body);

            if (hasGradientOverlay(options)) {
                container.append(this.createGradientOverlay(body, {
                    baseColor: this.color
                },
                    deepExtend({}, options.overlay)
                ));
            }
        },

        createLines: function (container, options) {
            this.drawLines(container, options, this.lines, options.line);
        },

        drawLines: function (container, options, lines, lineOptions) {
            var lineStyle = {
                stroke: {
                    color: lineOptions.color || this.color,
                    opacity: valueOrDefault(lineOptions.opacity, options.opacity),
                    width: lineOptions.width,
                    dashType: lineOptions.dashType,
                    lineCap: "butt"
                }
            };

            for (var i = 0; i < lines.length; i++) {
                var line = draw.Path.fromPoints(lines[i], lineStyle);
                alignPathToPixel(line);
                container.append(line);
            }
        },

        getBorderColor: function () {
            var point = this,
                options = point.options,
                border = options.border,
                borderColor = border.color;

            if (!defined(borderColor)) {
                borderColor =
                    new Color(point.color).brightness(border._brightness).toHex();
            }

            return borderColor;
        },

        createOverlay: function () {
            var overlay = draw.Path.fromRect(this.box.toRect(), {
                fill: {
                    color: WHITE,
                    opacity: 0
                },
                stroke: null
            });

            this.visual.append(overlay);
        },

        createHighlight: function () {
            var highlight = this.options.highlight;
            var normalColor = this.color;

            this.color = highlight.color || this.color;
            var overlay = this.mainVisual(
                deepExtend({}, this.options, {
                    line: {
                        color: this.getBorderColor()
                    }
                }, highlight)
            );
            this.color = normalColor;

            return overlay;
        },

        tooltipAnchor: function () {
            var point = this,
                box = point.box,
                clipBox = point.owner.pane.clipBox() || box;

            return new Point2D(box.x2 + TOOLTIP_OFFSET, math.max(box.y1, clipBox.y1) + TOOLTIP_OFFSET);
        },

        formatValue: function (format) {
            var point = this;
            return point.owner.formatPointValue(point, format);
        }
    });
    deepExtend(Candlestick.fn, PointEventsMixin);
    deepExtend(Candlestick.fn, NoteMixin);

    var CandlestickChart = CategoricalChart.extend({
        options: {},

        reflowCategories: function (categorySlots) {
            var chart = this,
                children = chart.children,
                childrenLength = children.length,
                i;

            for (i = 0; i < childrenLength; i++) {
                children[i].reflow(categorySlots[i]);
            }
        },

        addValue: function (data, fields) {
            var chart = this;
            var categoryIx = fields.categoryIx;
            var category = fields.category;
            var series = fields.series;
            var seriesIx = fields.seriesIx;
            var options = chart.options;
            var value = data.valueFields;
            var children = chart.children;
            var valueParts = chart.splitValue(value);
            var hasValue = areNumbers(valueParts);
            var categoryPoints = chart.categoryPoints[categoryIx];
            var dataItem = series.data[categoryIx];
            var point, cluster;

            if (!categoryPoints) {
                chart.categoryPoints[categoryIx] = categoryPoints = [];
            }

            if (hasValue) {
                point = chart.createPoint(data, fields);
            }

            cluster = children[categoryIx];
            if (!cluster) {
                cluster = new ClusterLayout({
                    vertical: options.invertAxes,
                    gap: options.gap,
                    spacing: options.spacing
                });
                chart.append(cluster);
            }

            if (point) {
                chart.updateRange(value, fields);

                cluster.append(point);

                point.categoryIx = categoryIx;
                point.category = category;
                point.series = series;
                point.seriesIx = seriesIx;
                point.owner = chart;
                point.dataItem = dataItem;
                point.noteText = data.fields.noteText;
            }

            chart.points.push(point);
            categoryPoints.push(point);
        },

        pointType: function () {
            return Candlestick;
        },

        createPoint: function (data, fields) {
            var chart = this;
            var categoryIx = fields.categoryIx;
            var category = fields.category;
            var series = fields.series;
            var seriesIx = fields.seriesIx;
            var value = data.valueFields;
            var pointOptions = deepExtend({}, series);
            var pointType = chart.pointType();
            var color = data.fields.color || series.color;

            pointOptions = chart.evalPointOptions(
                pointOptions, value, category, categoryIx, series, seriesIx
            );

            if (series.type == CANDLESTICK) {
                if (value.open > value.close) {
                    color = data.fields.downColor || series.downColor || series.color;
                }
            }

            if (kendo.isFunction(series.color)) {
                color = pointOptions.color;
            }

            var point = new pointType(value, pointOptions);
            point.color = color;

            return point;
        },

        splitValue: function (value) {
            return [value.low, value.open, value.close, value.high];
        },

        updateRange: function (value, fields) {
            var chart = this,
                axisName = fields.series.axis,
                axisRange = chart.valueAxisRanges[axisName],
                parts = chart.splitValue(value);

            axisRange = chart.valueAxisRanges[axisName] =
                axisRange || { min: MAX_VALUE, max: MIN_VALUE };

            axisRange = chart.valueAxisRanges[axisName] = {
                min: math.min.apply(math, parts.concat([axisRange.min])),
                max: math.max.apply(math, parts.concat([axisRange.max]))
            };
        },

        formatPointValue: function (point, format) {
            var value = point.value;

            return autoFormat(format,
                value.open, value.high,
                value.low, value.close, point.category
            );
        }
    });

    var OHLCPoint = Candlestick.extend({
        reflow: function (box) {
            var point = this,
                options = point.options,
                chart = point.owner,
                value = point.value,
                valueAxis = chart.seriesValueAxis(options),
                oPoints = [], cPoints = [], lhPoints = [],
                mid, oSlot, cSlot, lhSlot;

            lhSlot = valueAxis.getSlot(value.low, value.high);
            oSlot = valueAxis.getSlot(value.open, value.open);
            cSlot = valueAxis.getSlot(value.close, value.close);

            oSlot.x1 = cSlot.x1 = lhSlot.x1 = box.x1;
            oSlot.x2 = cSlot.x2 = lhSlot.x2 = box.x2;

            mid = lhSlot.center().x;

            oPoints.push([oSlot.x1, oSlot.y1]);
            oPoints.push([mid, oSlot.y1]);
            cPoints.push([mid, cSlot.y1]);
            cPoints.push([cSlot.x2, cSlot.y1]);
            lhPoints.push([mid, lhSlot.y1]);
            lhPoints.push([mid, lhSlot.y2]);

            point.lines = [
                oPoints, cPoints, lhPoints
            ];

            point.box = lhSlot.clone().wrap(oSlot.clone().wrap(cSlot));

            point.reflowNote();
        },

        createBody: $.noop
    });

    var OHLCChart = CandlestickChart.extend({
        pointType: function () {
            return OHLCPoint;
        }
    });

    var BoxPlotChart = CandlestickChart.extend({
        addValue: function (data, fields) {
            var chart = this;
            var categoryIx = fields.categoryIx;
            var category = fields.category;
            var series = fields.series;
            var seriesIx = fields.seriesIx;
            var options = chart.options;
            var children = chart.children;
            var value = data.valueFields;
            var valueParts = chart.splitValue(value);
            var hasValue = areNumbers(valueParts);
            var categoryPoints = chart.categoryPoints[categoryIx];
            var dataItem = series.data[categoryIx];
            var point, cluster;

            if (!categoryPoints) {
                chart.categoryPoints[categoryIx] = categoryPoints = [];
            }

            if (hasValue) {
                point = chart.createPoint(data, fields);
            }

            cluster = children[categoryIx];
            if (!cluster) {
                cluster = new ClusterLayout({
                    vertical: options.invertAxes,
                    gap: options.gap,
                    spacing: options.spacing
                });
                chart.append(cluster);
            }

            if (point) {
                chart.updateRange(value, fields);

                cluster.append(point);

                point.categoryIx = categoryIx;
                point.category = category;
                point.series = series;
                point.seriesIx = seriesIx;
                point.owner = chart;
                point.dataItem = dataItem;
            }

            chart.points.push(point);
            categoryPoints.push(point);
        },

        pointType: function () {
            return BoxPlot;
        },

        splitValue: function (value) {
            return [
                value.lower, value.q1, value.median,
                value.q3, value.upper
            ];
        },

        updateRange: function (value, fields) {
            var chart = this,
                axisName = fields.series.axis,
                axisRange = chart.valueAxisRanges[axisName],
                parts = chart.splitValue(value).concat(
                    chart.filterOutliers(value.outliers));

            if (defined(value.mean)) {
                parts = parts.concat(value.mean);
            }

            axisRange = chart.valueAxisRanges[axisName] =
                axisRange || { min: MAX_VALUE, max: MIN_VALUE };

            axisRange = chart.valueAxisRanges[axisName] = {
                min: math.min.apply(math, parts.concat([axisRange.min])),
                max: math.max.apply(math, parts.concat([axisRange.max]))
            };
        },

        formatPointValue: function (point, format) {
            var value = point.value;

            return autoFormat(format,
                value.lower, value.q1, value.median,
                value.q3, value.upper, value.mean, point.category
            );
        },

        filterOutliers: function (items) {
            var length = (items || []).length,
                result = [],
                i, item;

            for (i = 0; i < length; i++) {
                item = items[i];
                if (defined(item)) {
                    appendIfNotNull(result, item);
                }
            }

            return result;
        }
    });

    var BoxPlot = Candlestick.extend({
        init: function (value, options) {
            var point = this;

            ChartElement.fn.init.call(point, options);
            point.value = value;

            point.createNote();
        },

        options: {
            border: {
                _brightness: 0.8
            },
            line: {
                width: 2
            },
            mean: {
                width: 2,
                dashType: "dash"
            },
            overlay: {
                gradient: GLASS
            },
            tooltip: {
                format: "<table style='text-align: left;'>" +
                        "<th colspan='2'>{6:d}</th>" +
                        "<tr><td>Lower:</td><td>{0:C}</td></tr>" +
                        "<tr><td>Q1:</td><td>{1:C}</td></tr>" +
                        "<tr><td>Median:</td><td>{2:C}</td></tr>" +
                        "<tr><td>Mean:</td><td>{5:C}</td></tr>" +
                        "<tr><td>Q3:</td><td>{3:C}</td></tr>" +
                        "<tr><td>Upper:</td><td>{4:C}</td></tr>" +
                        "</table>"
            },
            highlight: {
                opacity: 1,
                border: {
                    width: 1,
                    opacity: 1
                },
                line: {
                    width: 1,
                    opacity: 1
                }
            },
            notes: {
                visible: true,
                label: {}
            },
            outliers: {
                visible: true,
                size: LINE_MARKER_SIZE,
                type: CROSS,
                background: WHITE,
                border: {
                    width: 2,
                    opacity: 1
                },
                opacity: 0
            },
            extremes: {
                visible: true,
                size: LINE_MARKER_SIZE,
                type: CIRCLE,
                background: WHITE,
                border: {
                    width: 2,
                    opacity: 1
                },
                opacity: 0
            }
        },

        reflow: function (box) {
            var point = this,
                options = point.options,
                chart = point.owner,
                value = point.value,
                valueAxis = chart.seriesValueAxis(options),
                mid, whiskerSlot, boxSlot, medianSlot, meanSlot;

            boxSlot = valueAxis.getSlot(value.q1, value.q3);
            point.boxSlot = boxSlot;

            whiskerSlot = valueAxis.getSlot(value.lower, value.upper);
            medianSlot = valueAxis.getSlot(value.median);

            boxSlot.x1 = whiskerSlot.x1 = box.x1;
            boxSlot.x2 = whiskerSlot.x2 = box.x2;

            point.realBody = boxSlot;

            if (value.mean) {
                meanSlot = valueAxis.getSlot(value.mean);
                point.meanPoints = [
                    [[box.x1, meanSlot.y1], [box.x2, meanSlot.y1]]
                ];
            }

            mid = whiskerSlot.center().x;
            point.whiskerPoints = [[
                [mid - 5, whiskerSlot.y1], [mid + 5, whiskerSlot.y1],
                [mid, whiskerSlot.y1], [mid, boxSlot.y1]
            ], [
                [mid - 5, whiskerSlot.y2], [mid + 5, whiskerSlot.y2],
                [mid, whiskerSlot.y2], [mid, boxSlot.y2]
            ]];

            point.medianPoints = [
                [[box.x1, medianSlot.y1], [box.x2, medianSlot.y1]]
            ];

            point.box = whiskerSlot.clone().wrap(boxSlot);

            point.reflowNote();
        },

        renderOutliers: function (options) {
            var point = this,
                markers = options.markers || {},
                value = point.value,
                outliers = value.outliers || [],
                valueAxis = point.owner.seriesValueAxis(options),
                outerFence = math.abs(value.q3 - value.q1) * 3,
                markersBorder, markerBox, shape, outlierValue, i;

            var elements = [];

            for (i = 0; i < outliers.length; i++) {
                outlierValue = outliers[i];
                if (outlierValue < value.q3 + outerFence && outlierValue > value.q1 - outerFence) {
                    markers = options.outliers;
                } else {
                    markers = options.extremes;
                }
                markersBorder = deepExtend({}, markers.border);

                if (!defined(markersBorder.color)) {
                    if (defined(point.color)) {
                        markersBorder.color = point.color;
                    } else {
                        markersBorder.color =
                            new Color(markers.background).brightness(BAR_BORDER_BRIGHTNESS).toHex();
                    }
                }

                shape = new ShapeElement({
                    type: markers.type,
                    width: markers.size,
                    height: markers.size,
                    rotation: markers.rotation,
                    background: markers.background,
                    border: markersBorder,
                    opacity: markers.opacity
                });

                shape.value = outlierValue;

                elements.push(shape);
            }

            this.reflowOutliers(elements);
            return elements;
        },

        reflowOutliers: function (outliers) {
            var valueAxis = this.owner.seriesValueAxis(this.options);
            var centerX = this.box.center().x;

            for (var i = 0; i < outliers.length; i++) {
                var outlierValue = outliers[i].value;
                var markerBox = valueAxis.getSlot(outlierValue).move(centerX);

                this.box = this.box.wrap(markerBox);
                outliers[i].reflow(markerBox);
            }
        },

        mainVisual: function (options) {
            var group = Candlestick.fn.mainVisual.call(this, options);

            var outliers = this.renderOutliers(options);
            for (var i = 0; i < outliers.length; i++) {
                var element = outliers[i].getElement();
                if (element) {
                    group.append(element);
                }
            }

            return group;
        },

        createLines: function (container, options) {
            this.drawLines(container, options, this.whiskerPoints, options.line);
            this.drawLines(container, options, this.medianPoints, options.median);
            this.drawLines(container, options, this.meanPoints, options.mean);
        },

        getBorderColor: function () {
            if (this.color) {
                return this.color;
            }

            return Candlestick.getBorderColor.call(this);
        }
    });
    deepExtend(BoxPlot.fn, PointEventsMixin);

    // TODO: Rename to Segment?
    var PieSegment = ChartElement.extend({
        init: function (value, sector, options) {
            var segment = this;

            segment.value = value;
            segment.sector = sector;

            ChartElement.fn.init.call(segment, options);
        },

        options: {
            color: WHITE,
            overlay: {
                gradient: ROUNDED_BEVEL
            },
            border: {
                width: 0.5
            },
            labels: {
                visible: false,
                distance: 35,
                font: DEFAULT_FONT,
                margin: getSpacing(0.5),
                align: CIRCLE,
                zIndex: 1,
                position: OUTSIDE_END
            },
            animation: {
                type: PIE
            },
            highlight: {
                visible: true,
                border: {
                    width: 1
                }
            },
            visible: true
        },

        render: function () {
            var segment = this,
                options = segment.options,
                labels = options.labels,
                labelText = segment.value,
                labelTemplate;

            if (segment._rendered || segment.visible === false) {
                return;
            } else {
                segment._rendered = true;
            }

            if (labels.template) {
                labelTemplate = template(labels.template);
                labelText = labelTemplate({
                    dataItem: segment.dataItem,
                    category: segment.category,
                    value: segment.value,
                    series: segment.series,
                    percentage: segment.percentage
                });
            } else if (labels.format) {
                labelText = autoFormat(labels.format, labelText);
            }

            if (labels.visible && labelText) {
                segment.label = new TextBox(labelText, deepExtend({}, labels, {
                    align: CENTER,
                    vAlign: "",
                    animation: {
                        type: FADEIN,
                        delay: segment.animationDelay
                    }
                }));

                segment.append(segment.label);
            }
        },

        reflow: function (targetBox) {
            var segment = this;

            segment.render();
            segment.box = targetBox;
            segment.reflowLabel();
        },

        reflowLabel: function () {
            var segment = this,
                sector = segment.sector.clone(),
                options = segment.options,
                label = segment.label,
                labelsOptions = options.labels,
                labelsDistance = labelsOptions.distance,
                angle = sector.middle(),
                lp, x1, labelWidth, labelHeight;

            if (label) {
                labelHeight = label.box.height();
                labelWidth = label.box.width();
                if (labelsOptions.position == CENTER) {
                    sector.r = math.abs((sector.r - labelHeight) / 2) + labelHeight;
                    lp = sector.point(angle);
                    label.reflow(Box2D(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
                } else if (labelsOptions.position == INSIDE_END) {
                    sector.r = sector.r - labelHeight / 2;
                    lp = sector.point(angle);
                    label.reflow(Box2D(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
                } else {
                    lp = sector.clone().expand(labelsDistance).point(angle);
                    if (lp.x >= sector.c.x) {
                        x1 = lp.x + labelWidth;
                        label.orientation = RIGHT;
                    } else {
                        x1 = lp.x - labelWidth;
                        label.orientation = LEFT;
                    }
                    label.reflow(Box2D(x1, lp.y - labelHeight, lp.x, lp.y));
                }
            }
        },

        createVisual: function () {
            var segment = this,
                sector = segment.sector,
                options = segment.options,
                borderOptions = options.border || {},
                border = borderOptions.width > 0 ? {
                    stroke: {
                        color: borderOptions.color,
                        width: borderOptions.width,
                        opacity: borderOptions.opacity,
                        dashType: borderOptions.dashType
                    }
                } : {},
                elements = [],
                color = options.color,
                fill = {
                    color: color,
                    opacity: options.opacity
                },
                visual;

            ChartElement.fn.createVisual.call(this);
            if (segment.value) {
                visual = segment.createSegment(sector, deepExtend({
                    fill: fill,
                    stroke: {
                        opacity: options.opacity
                    },
                    zIndex: options.zIndex
                }, border));

                this.visual.append(visual);

                if (hasGradientOverlay(options)) {
                    this.visual.append(this.createGradientOverlay(visual, {
                        baseColor: color,
                        fallbackFill: fill
                    }, deepExtend({
                        center: [sector.c.x, sector.c.y],
                        innerRadius: sector.ir,
                        radius: sector.r,
                        userSpace: true
                    }, options.overlay)
                    ));
                }
            }
        },

        createSegment: function (sector, options) {
            if (options.singleSegment) {
                return new draw.Circle(new geom.Circle(new geom.Point(sector.c.x, sector.c.y), sector.r), options);
            } else {
                return ShapeBuilder.current.createRing(sector, options);
            }
        },

        createAnimation: function () {
            var options = this.options;

            var center = this.sector.c;
            deepExtend(options, {
                animation: {
                    center: [center.x, center.y],
                    delay: this.animationDelay
                }
            });

            ChartElement.fn.createAnimation.call(this);
        },

        createHighlight: function (options) {
            var segment = this,
                highlight = segment.options.highlight || {},
                border = highlight.border || {};

            return segment.createSegment(segment.sector, deepExtend({}, options, {
                fill: {
                    color: highlight.color,
                    opacity: highlight.opacity
                },
                stroke: {
                    opacity: border.opacity,
                    width: border.width,
                    color: border.color
                }
            }));
        },

        tooltipAnchor: function (width, height) {
            var point = this,
                box = point.sector.adjacentBox(TOOLTIP_OFFSET, width, height);

            return new Point2D(box.x1, box.y1);
        },

        formatValue: function (format) {
            var point = this;

            return point.owner.formatPointValue(point, format);
        }
    });
    deepExtend(PieSegment.fn, PointEventsMixin);

    var PieChartMixin = {
        createLegendItem: function (value, point) {
            var chart = this,
                legendOptions = chart.options.legend || {},
                labelsOptions = legendOptions.labels || {},
                inactiveItems = legendOptions.inactiveItems || {},
                inactiveItemsLabels = inactiveItems.labels || {},
                text, labelTemplate, markerColor, itemLabelOptions,
                pointVisible;

            if (point && point.visibleInLegend !== false) {
                pointVisible = point.visible !== false;
                text = point.category || "";
                labelTemplate = pointVisible ? labelsOptions.template :
                    (inactiveItemsLabels.template || labelsOptions.template);

                if (labelTemplate) {
                    text = template(labelTemplate)({
                        text: text,
                        series: point.series,
                        dataItem: point.dataItem,
                        percentage: point.percentage,
                        value: value
                    });
                }

                if (pointVisible) {
                    itemLabelOptions = {};
                    markerColor = (point.series || {}).color;
                } else {
                    itemLabelOptions = {
                        color: inactiveItemsLabels.color,
                        font: inactiveItemsLabels.font
                    };
                    markerColor = (inactiveItems.markers || {}).color;
                }

                if (text) {
                    chart.legendItems.push({
                        pointIndex: point.index,
                        text: text,
                        series: point.series,
                        markerColor: markerColor,
                        labels: itemLabelOptions
                    });
                }
            }
        }
    };

    var PieChart = ChartElement.extend({
        init: function (plotArea, options) {
            var chart = this;

            ChartElement.fn.init.call(chart, options);

            chart.plotArea = plotArea;
            chart.points = [];
            chart.legendItems = [];
            chart.render();
        },

        options: {
            startAngle: 90,
            connectors: {
                width: 1,
                color: "#939393",
                padding: 4
            },
            inactiveItems: {
                markers: {},
                labels: {}
            }
        },

        render: function () {
            var chart = this;

            chart.traverseDataPoints(proxy(chart.addValue, chart));
        },

        traverseDataPoints: function (callback) {
            var chart = this,
                options = chart.options,
                colors = chart.plotArea.options.seriesColors || [],
                colorsCount = colors.length,
                series = options.series,
                seriesCount = series.length,
                currentSeries, pointData, fields, seriesIx,
                angle, data, anglePerValue, value, plotValue, explode,
                total, currentAngle, i, pointIx = 0;

            for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
                currentSeries = series[seriesIx];
                data = currentSeries.data;
                total = seriesTotal(currentSeries);
                anglePerValue = 360 / total;

                if (defined(currentSeries.startAngle)) {
                    currentAngle = currentSeries.startAngle;
                } else {
                    currentAngle = options.startAngle;
                }

                if (seriesIx != seriesCount - 1) {
                    if (currentSeries.labels.position == OUTSIDE_END) {
                        currentSeries.labels.position = CENTER;
                    }
                }

                for (i = 0; i < data.length; i++) {
                    pointData = SeriesBinder.current.bindPoint(currentSeries, i);
                    value = pointData.valueFields.value;
                    plotValue = math.abs(value);
                    fields = pointData.fields;
                    angle = round(plotValue * anglePerValue, DEFAULT_PRECISION);
                    explode = data.length != 1 && !!fields.explode;
                    if (!isFn(currentSeries.color)) {
                        currentSeries.color = fields.color || colors[i % colorsCount];
                    }

                    callback(value, new Ring(null, 0, 0, currentAngle, angle), {
                        owner: chart,
                        category: fields.category || "",
                        index: pointIx,
                        series: currentSeries,
                        seriesIx: seriesIx,
                        dataItem: data[i],
                        percentage: plotValue / total,
                        explode: explode,
                        visibleInLegend: fields.visibleInLegend,
                        visible: fields.visible,
                        zIndex: seriesCount - seriesIx,
                        animationDelay: chart.animationDelay(i, seriesIx, seriesCount)
                    });

                    if (pointData.fields.visible !== false) {
                        currentAngle += angle;
                    }
                    pointIx++;
                }
                pointIx = 0;
            }
        },

        evalSegmentOptions: function (options, value, fields) {
            var series = fields.series;

            evalOptions(options, {
                value: value,
                series: series,
                dataItem: fields.dataItem,
                category: fields.category,
                percentage: fields.percentage
            }, { defaults: series._defaults, excluded: ["data", "template"] });
        },

        addValue: function (value, sector, fields) {
            var chart = this,
                segment;

            chart.createLegendItem(value, fields);

            if (fields.visible === false) {
                return;
            }

            var segmentOptions = deepExtend({}, fields.series, { index: fields.index });
            chart.evalSegmentOptions(segmentOptions, value, fields);

            segment = new PieSegment(value, sector, segmentOptions);
            extend(segment, fields);
            chart.append(segment);
            chart.points.push(segment);
        },

        reflow: function (targetBox) {
            var chart = this,
                options = chart.options,
                box = targetBox.clone(),
                space = 5,
                minWidth = math.min(box.width(), box.height()),
                halfMinWidth = minWidth / 2,
                defaultPadding = minWidth - minWidth * 0.85,
                padding = valueOrDefault(options.padding, defaultPadding),
                newBox = Box2D(box.x1, box.y1,
                    box.x1 + minWidth, box.y1 + minWidth),
                newBoxCenter = newBox.center(),
                seriesConfigs = chart.seriesConfigs || [],
                boxCenter = box.center(),
                points = chart.points,
                count = points.length,
                seriesCount = options.series.length,
                leftSideLabels = [],
                rightSideLabels = [],
                seriesConfig, seriesIndex, label,
                segment, sector, r, i, c;

            padding = padding > halfMinWidth - space ? halfMinWidth - space : padding;
            newBox.translate(boxCenter.x - newBoxCenter.x, boxCenter.y - newBoxCenter.y);
            r = halfMinWidth - padding;
            c = Point2D(
                r + newBox.x1 + padding,
                r + newBox.y1 + padding
            );

            for (i = 0; i < count; i++) {
                segment = points[i];

                sector = segment.sector;
                sector.r = r;
                sector.c = c;
                seriesIndex = segment.seriesIx;
                if (seriesConfigs.length) {
                    seriesConfig = seriesConfigs[seriesIndex];
                    sector.ir = seriesConfig.ir;
                    sector.r = seriesConfig.r;
                }

                if (seriesIndex == seriesCount - 1 && segment.explode) {
                    sector.c = sector.clone().radius(sector.r * 0.15).point(sector.middle());
                }

                segment.reflow(newBox);

                label = segment.label;
                if (label) {
                    if (label.options.position === OUTSIDE_END) {
                        if (seriesIndex == seriesCount - 1) {
                            if (label.orientation === RIGHT) {
                                rightSideLabels.push(label);
                            } else {
                                leftSideLabels.push(label);
                            }
                        }
                    }
                }
            }

            if (leftSideLabels.length > 0) {
                leftSideLabels.sort(chart.labelComparator(true));
                chart.leftLabelsReflow(leftSideLabels);
            }

            if (rightSideLabels.length > 0) {
                rightSideLabels.sort(chart.labelComparator(false));
                chart.rightLabelsReflow(rightSideLabels);
            }

            chart.box = newBox;
        },

        leftLabelsReflow: function (labels) {
            var chart = this,
                distances = chart.distanceBetweenLabels(labels);

            chart.distributeLabels(distances, labels);
        },

        rightLabelsReflow: function (labels) {
            var chart = this,
                distances = chart.distanceBetweenLabels(labels);

            chart.distributeLabels(distances, labels);
        },

        distanceBetweenLabels: function (labels) {
            var chart = this,
                points = chart.points,
                segment = points[points.length - 1],
                sector = segment.sector,
                firstBox = labels[0].box,
                count = labels.length - 1,
                lr = sector.r + segment.options.labels.distance,
                distances = [],
                secondBox, distance, i;

            distance = round(firstBox.y1 - (sector.c.y - lr - firstBox.height() - firstBox.height() / 2));
            distances.push(distance);
            for (i = 0; i < count; i++) {
                firstBox = labels[i].box;
                secondBox = labels[i + 1].box;
                distance = round(secondBox.y1 - firstBox.y2);
                distances.push(distance);
            }
            distance = round(sector.c.y + lr - labels[count].box.y2 - labels[count].box.height() / 2);
            distances.push(distance);

            return distances;
        },

        distributeLabels: function (distances, labels) {
            var chart = this,
                count = distances.length,
                remaining, left, right, i;

            for (i = 0; i < count; i++) {
                left = right = i;
                remaining = -distances[i];
                while (remaining > 0 && (left >= 0 || right < count)) {
                    remaining = chart._takeDistance(distances, i, --left, remaining);
                    remaining = chart._takeDistance(distances, i, ++right, remaining);
                }
            }

            chart.reflowLabels(distances, labels);
        },

        _takeDistance: function (distances, anchor, position, amount) {
            if (distances[position] > 0) {
                var available = math.min(distances[position], amount);
                amount -= available;
                distances[position] -= available;
                distances[anchor] += available;
            }

            return amount;
        },

        reflowLabels: function (distances, labels) {
            var chart = this,
                points = chart.points,
                segment = points[points.length - 1],
                sector = segment.sector,
                labelsCount = labels.length,
                labelOptions = segment.options.labels,
                labelDistance = labelOptions.distance,
                boxY = sector.c.y - (sector.r + labelDistance) - labels[0].box.height(),
                label, boxX, box, i;

            distances[0] += 2;
            for (i = 0; i < labelsCount; i++) {
                label = labels[i];
                boxY += distances[i];
                box = label.box;
                boxX = chart.hAlignLabel(
                    box.x2,
                    sector.clone().expand(labelDistance),
                    boxY,
                    boxY + box.height(),
                    label.orientation == RIGHT);

                if (label.orientation == RIGHT) {
                    if (labelOptions.align !== CIRCLE) {
                        boxX = sector.r + sector.c.x + labelDistance;
                    }
                    label.reflow(Box2D(boxX + box.width(), boxY,
                        boxX, boxY));
                } else {
                    if (labelOptions.align !== CIRCLE) {
                        boxX = sector.c.x - sector.r - labelDistance;
                    }
                    label.reflow(Box2D(boxX - box.width(), boxY,
                        boxX, boxY));
                }

                boxY += box.height();
            }
        },

        createVisual: function () {
            var chart = this,
                options = chart.options,
                connectors = options.connectors,
                points = chart.points,
                connectorLine,
                count = points.length,
                space = 4,
                sector, angle, segment,
                seriesIx, label, i;

            ChartElement.fn.createVisual.call(this);
            this._connectorLines = [];
            for (i = 0; i < count; i++) {
                segment = points[i];
                sector = segment.sector;
                angle = sector.middle();
                label = segment.label;
                seriesIx = { seriesId: segment.seriesIx };

                if (label) {
                    connectorLine = new draw.Path({
                        stroke: {
                            color: connectors.color,
                            width: connectors.width
                        },
                        animation: {
                            type: FADEIN,
                            delay: segment.animationDelay
                        }
                    });
                    if (label.options.position === OUTSIDE_END && segment.value !== 0) {
                        var box = label.box,
                            centerPoint = sector.c,
                            start = sector.point(angle),
                            middle = Point2D(box.x1, box.center().y),
                            sr, end, crossing;

                        start = sector.clone().expand(connectors.padding).point(angle);
                        connectorLine.moveTo(start.x, start.y);
                        // TODO: Extract into a method to remove duplication
                        if (label.orientation == RIGHT) {
                            end = Point2D(box.x1 - connectors.padding, box.center().y);
                            crossing = intersection(centerPoint, start, middle, end);
                            middle = Point2D(end.x - space, end.y);
                            crossing = crossing || middle;
                            crossing.x = math.min(crossing.x, middle.x);

                            if (chart.pointInCircle(crossing, sector.c, sector.r + space) ||
                                crossing.x < sector.c.x) {
                                sr = sector.c.x + sector.r + space;
                                if (segment.options.labels.align !== COLUMN) {
                                    if (sr < middle.x) {
                                        connectorLine.lineTo(sr, start.y);
                                    } else {
                                        connectorLine.lineTo(start.x + space * 2, start.y);
                                    }
                                } else {
                                    connectorLine.lineTo(sr, start.y);
                                }
                                connectorLine.lineTo(middle.x, end.y);
                            } else {
                                crossing.y = end.y;
                                connectorLine.lineTo(crossing.x, crossing.y);
                            }
                        } else {
                            end = Point2D(box.x2 + connectors.padding, box.center().y);
                            crossing = intersection(centerPoint, start, middle, end);
                            middle = Point2D(end.x + space, end.y);
                            crossing = crossing || middle;
                            crossing.x = math.max(crossing.x, middle.x);

                            if (chart.pointInCircle(crossing, sector.c, sector.r + space) ||
                                crossing.x > sector.c.x) {
                                sr = sector.c.x - sector.r - space;
                                if (segment.options.labels.align !== COLUMN) {
                                    if (sr > middle.x) {
                                        connectorLine.lineTo(sr, start.y);
                                    } else {
                                        connectorLine.lineTo(start.x - space * 2, start.y);
                                    }
                                } else {
                                    connectorLine.lineTo(sr, start.y);
                                }
                                connectorLine.lineTo(middle.x, end.y);
                            } else {
                                crossing.y = end.y;
                                connectorLine.lineTo(crossing.x, crossing.y);
                            }
                        }

                        connectorLine.lineTo(end.x, end.y);

                        this._connectorLines.push(connectorLine);
                        this.visual.append(connectorLine);
                    }
                }
            }
        },

        labelComparator: function (reverse) {
            reverse = (reverse) ? -1 : 1;

            return function (a, b) {
                a = (a.parent.sector.middle() + 270) % 360;
                b = (b.parent.sector.middle() + 270) % 360;
                return (a - b) * reverse;
            };
        },

        hAlignLabel: function (originalX, sector, y1, y2, direction) {
            var cx = sector.c.x,
                cy = sector.c.y,
                r = sector.r,
                t = math.min(math.abs(cy - y1), math.abs(cy - y2));

            if (t > r) {
                return originalX;
            } else {
                return cx + math.sqrt((r * r) - (t * t)) * (direction ? 1 : -1);
            }
        },

        pointInCircle: function (point, c, r) {
            return sqr(c.x - point.x) + sqr(c.y - point.y) < sqr(r);
        },

        formatPointValue: function (point, format) {
            return autoFormat(format, point.value);
        },

        animationDelay: function (categoryIndex) {
            return categoryIndex * PIE_SECTOR_ANIM_DELAY;
        }
    });

    deepExtend(PieChart.fn, PieChartMixin);

    var DonutSegment = PieSegment.extend({
        options: {
            overlay: {
                gradient: ROUNDED_GLASS
            },
            labels: {
                position: CENTER
            },
            animation: {
                type: PIE
            }
        },

        reflowLabel: function () {
            var segment = this,
                sector = segment.sector.clone(),
                options = segment.options,
                label = segment.label,
                labelsOptions = options.labels,
                lp,
                angle = sector.middle(),
                labelHeight;

            if (label) {
                labelHeight = label.box.height();
                if (labelsOptions.position == CENTER) {
                    sector.r -= (sector.r - sector.ir) / 2;
                    lp = sector.point(angle);
                    label.reflow(new Box2D(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
                } else {
                    PieSegment.fn.reflowLabel.call(segment);
                }
            }
        },

        createSegment: function (sector, options) {
            return ShapeBuilder.current.createRing(sector, options);
        }
    });
    deepExtend(DonutSegment.fn, PointEventsMixin);

    var DonutChart = PieChart.extend({
        options: {
            startAngle: 90,
            connectors: {
                width: 1,
                color: "#939393",
                padding: 4
            }
        },

        addValue: function (value, sector, fields) {
            var chart = this,
                segment;

            chart.createLegendItem(value, fields);

            if (!value || fields.visible === false) {
                return;
            }

            var segmentOptions = deepExtend({}, fields.series, { index: fields.index });
            chart.evalSegmentOptions(segmentOptions, value, fields);

            segment = new DonutSegment(value, sector, segmentOptions);
            extend(segment, fields);
            chart.append(segment);
            chart.points.push(segment);
        },

        reflow: function (targetBox) {
            var chart = this,
                options = chart.options,
                box = targetBox.clone(),
                space = 5,
                minWidth = math.min(box.width(), box.height()),
                halfMinWidth = minWidth / 2,
                defaultPadding = minWidth - minWidth * 0.85,
                padding = valueOrDefault(options.padding, defaultPadding),
                series = options.series,
                currentSeries,
                seriesCount = series.length,
                seriesWithoutSize = 0,
                holeSize, totalSize, size,
                margin = 0, i, r, ir = 0,
                currentSize = 0;

            chart.seriesConfigs = [];
            padding = padding > halfMinWidth - space ? halfMinWidth - space : padding;
            totalSize = halfMinWidth - padding;

            for (i = 0; i < seriesCount; i++) {
                currentSeries = series[i];
                if (i === 0) {
                    if (defined(currentSeries.holeSize)) {
                        holeSize = currentSeries.holeSize;
                        totalSize -= currentSeries.holeSize;
                    }
                }

                if (defined(currentSeries.size)) {
                    totalSize -= currentSeries.size;
                } else {
                    seriesWithoutSize++;
                }

                if (defined(currentSeries.margin) && i != seriesCount - 1) {
                    totalSize -= currentSeries.margin;
                }
            }

            if (!defined(holeSize)) {
                currentSize = (halfMinWidth - padding) / (seriesCount + 0.75);
                holeSize = currentSize * 0.75;
                totalSize -= holeSize;
            }

            ir = holeSize;

            for (i = 0; i < seriesCount; i++) {
                currentSeries = series[i];
                size = valueOrDefault(currentSeries.size, totalSize / seriesWithoutSize);
                ir += margin;
                r = ir + size;
                chart.seriesConfigs.push({ ir: ir, r: r });
                margin = currentSeries.margin || 0;
                ir = r;
            }

            PieChart.fn.reflow.call(chart, targetBox);
        },

        animationDelay: function (categoryIndex, seriesIndex, seriesCount) {
            return categoryIndex * DONUT_SECTOR_ANIM_DELAY +
                (INITIAL_ANIMATION_DURATION * (seriesIndex + 1) / (seriesCount + 1));
        }
    });

    var WaterfallChart = BarChart.extend({
        render: function () {
            BarChart.fn.render.call(this);
            this.createSegments();
        },

        traverseDataPoints: function (callback) {
            var series = this.options.series;
            var categories = this.categoryAxis.options.categories || [];
            var totalCategories = categoriesCount(series);
            var isVertical = !this.options.invertAxes;

            for (var seriesIx = 0; seriesIx < series.length; seriesIx++) {
                var currentSeries = series[seriesIx];
                var total = 0;
                var runningTotal = 0;

                for (var categoryIx = 0; categoryIx < totalCategories; categoryIx++) {
                    var data = SeriesBinder.current.bindPoint(currentSeries, categoryIx);
                    var value = data.valueFields.value;
                    var summary = data.fields.summary;

                    var from = total;
                    var to;
                    if (summary) {
                        if (summary.toLowerCase() === "total") {
                            data.valueFields.value = total;
                            from = 0;
                            to = total;
                        } else {
                            data.valueFields.value = runningTotal;
                            to = from - runningTotal;
                            runningTotal = 0;
                        }
                    } else if (isNumber(value)) {
                        runningTotal += value;
                        total += value;
                        to = total;
                    }

                    callback(data, {
                        category: categories[categoryIx],
                        categoryIx: categoryIx,
                        series: currentSeries,
                        seriesIx: seriesIx,
                        total: total,
                        runningTotal: runningTotal,
                        from: from,
                        to: to,
                        isVertical: isVertical
                    });
                }
            }
        },

        updateRange: function (value, fields) {
            BarChart.fn.updateRange.call(this, { value: fields.to }, fields);
        },

        aboveAxis: function (point) {
            return point.value >= 0;
        },

        plotRange: function (point) {
            return [point.from, point.to];
        },

        createSegments: function () {
            var series = this.options.series;
            var seriesPoints = this.seriesPoints;
            var segments = this.segments = [];

            for (var seriesIx = 0; seriesIx < series.length; seriesIx++) {
                var currentSeries = series[seriesIx];
                var points = seriesPoints[seriesIx];

                if (points) {
                    var prevPoint;
                    for (var pointIx = 0; pointIx < points.length; pointIx++) {
                        var point = points[pointIx];

                        if (point && prevPoint) {
                            var segment = new WaterfallSegment(prevPoint, point, currentSeries);
                            segments.push(segment);
                            this.append(segment);
                        }

                        prevPoint = point;
                    }
                }
            }
        }
    });

    var WaterfallSegment = ChartElement.extend({
        init: function (from, to, series) {
            var segment = this;

            ChartElement.fn.init.call(segment);

            segment.from = from;
            segment.to = to;
            segment.series = series;
        },

        options: {
            animation: {
                type: FADEIN,
                delay: INITIAL_ANIMATION_DURATION
            }
        },

        linePoints: function () {
            var points = [];
            var from = this.from;
            var fromBox = from.box;
            var toBox = this.to.box;

            if (from.isVertical) {
                var y = from.aboveAxis ? fromBox.y1 : fromBox.y2;
                points.push(
                    [fromBox.x1, y],
                    [toBox.x2, y]
                );
            } else {
                var x = from.aboveAxis ? fromBox.x2 : fromBox.x1;
                points.push(
                    [x, fromBox.y1],
                    [x, toBox.y2]
                );
            }

            return points;
        },

        createVisual: function () {
            ChartElement.fn.createVisual.call(this);

            var options = this.options;
            var line = this.series.line || {};

            this.visual.append(draw.Path.fromPoints(this.linePoints(), {
                stroke: {
                    color: line.color,
                    width: line.width,
                    opacity: line.opacity,
                    dashType: line.dashType
                }
            }));
        }
    });

    var Pane = BoxElement.extend({
        init: function (options) {
            var pane = this;

            BoxElement.fn.init.call(pane, options);

            options = pane.options;
            pane.id = kendo.guid();

            pane.createTitle();

            pane.content = new ChartElement();
            pane.chartContainer = new ChartContainer({}, pane);
            pane.append(pane.content);

            pane.axes = [];
            pane.charts = [];
        },

        options: {
            zIndex: -1,
            shrinkToFit: true,
            title: {
                align: LEFT
            },
            visible: true
        },

        createTitle: function () {
            var pane = this;
            var titleOptions = pane.options.title;
            if (typeof titleOptions === OBJECT) {
                titleOptions = deepExtend({}, titleOptions, {
                    align: titleOptions.position,
                    position: TOP
                });
            }
            pane.title = Title.buildTitle(titleOptions, pane, Pane.fn.options.title);
        },

        appendAxis: function (axis) {
            var pane = this;

            pane.content.append(axis);
            pane.axes.push(axis);
            axis.pane = pane;
        },

        appendChart: function (chart) {
            var pane = this;
            if (pane.chartContainer.parent !== pane.content) {
                pane.content.append(pane.chartContainer);
            }
            pane.charts.push(chart);
            pane.chartContainer.append(chart);
            chart.pane = pane;
        },

        empty: function () {
            var pane = this,
                plotArea = pane.parent,
                i;

            if (plotArea) {
                for (i = 0; i < pane.axes.length; i++) {
                    plotArea.removeAxis(pane.axes[i]);
                }

                for (i = 0; i < pane.charts.length; i++) {
                    plotArea.removeChart(pane.charts[i]);
                }
            }

            pane.axes = [];
            pane.charts = [];

            pane.content.destroy();
            pane.content.children = [];
            pane.chartContainer.children = [];
        },

        reflow: function (targetBox) {
            var pane = this;

            // Content (such as charts) is rendered, but excluded from reflows
            var content;
            if (last(pane.children) === pane.content) {
                content = pane.children.pop();
            }

            BoxElement.fn.reflow.call(pane, targetBox);

            if (content) {
                pane.children.push(content);
            }

            if (pane.title) {
                pane.contentBox.y1 += pane.title.box.height();
            }
        },

        renderComplete: function () {
            if (this.options.visible) {
                this.createGridLines();
            }
        },

        stackRoot: function () {
            return this;
        },

        createGridLines: function () {
            var pane = this,
                axes = pane.axes,
                allAxes = axes.concat(pane.parent.axes),
                vGridLines = [],
                hGridLines = [],
                gridLines, i, j, axis,
                vertical, altAxis;

            // TODO
            // Is full combination really necessary?
            for (i = 0; i < axes.length; i++) {
                axis = axes[i];
                vertical = axis.options.vertical;
                gridLines = vertical ? vGridLines : hGridLines;
                for (j = 0; j < allAxes.length; j++) {
                    if (gridLines.length === 0) {
                        altAxis = allAxes[j];
                        if (vertical !== altAxis.options.vertical) {
                            append(gridLines, axis.createGridLines(altAxis));
                        }
                    }
                }
            }
        },

        refresh: function () {
            this.visual.clear();

            this.content.parent = null;
            this.content.createGradient = $.proxy(this.createGradient, this);
            this.content.renderVisual();
            this.content.parent = this;

            this.visual.append(this.content.visual);

            this.renderComplete();
        },

        clipBox: function () {
            return this.chartContainer.clipBox;
        }
    });

    var ChartContainer = ChartElement.extend({
        init: function (options, pane) {
            var container = this;
            ChartElement.fn.init.call(container, options);
            container.pane = pane;
        },

        shouldClip: function () {
            var container = this,
                children = container.children,
                length = children.length,
                i;

            for (i = 0; i < length; i++) {
                if (children[i].options.clip === true) {
                    return true;
                }
            }
            return false;
        },

        _clipBox: function () {
            var container = this,
                pane = container.pane,
                axes = pane.axes,
                length = axes.length,
                clipBox = pane.box.clone(),
                axisValueField, idx,
                lineBox, axis;

            for (idx = 0; idx < length; idx++) {
                axis = axes[idx];
                axisValueField = axis.options.vertical ? Y : X;
                lineBox = axis.lineBox();
                clipBox[axisValueField + 1] = lineBox[axisValueField + 1];
                clipBox[axisValueField + 2] = lineBox[axisValueField + 2];
            }

            return clipBox;
        },

        createVisual: function () {
            this.visual = new draw.Group({
                zIndex: 0
            });

            if (this.shouldClip()) {
                var clipBox = this.clipBox = this._clipBox();

                var clipRect = clipBox.toRect();
                var clipPath = draw.Path.fromRect(clipRect);
                this.visual.clip(clipPath);

                this.unclipLabels();
            }
        },

        stackRoot: function () {
            return this;
        },

        unclipLabels: function () {
            var container = this,
                charts = container.children,
                clipBox = container.clipBox,
                points, point,
                i, j, length;
            for (i = 0; i < charts.length; i++) {
                points = charts[i].points || {};
                length = points.length;

                for (j = 0; j < length; j++) {
                    point = points[j];
                    if (point && point.label && point.label.options.visible) {
                        if (point.box.overlaps(clipBox)) {
                            if (point.label.alignToClipBox) {
                                point.label.alignToClipBox(clipBox);
                            }
                            point.label.options.noclip = true;
                        }
                    }
                }
            }
        },

        destroy: function () {
            ChartElement.fn.destroy.call(this);
            delete this.parent;
        }
    });

    var PlotAreaBase = ChartElement.extend({
        init: function (series, options) {
            var plotArea = this;

            ChartElement.fn.init.call(plotArea, options);

            plotArea.series = series;
            plotArea.initSeries();
            plotArea.charts = [];
            plotArea.options.legend.items = [];
            plotArea.axes = [];
            plotArea.crosshairs = [];

            plotArea.createPanes();
            plotArea.render();
            plotArea.createCrosshairs();
        },

        options: {
            series: [],
            plotArea: {
                margin: {}
            },
            background: "",
            border: {
                color: BLACK,
                width: 0
            },
            legend: {
                inactiveItems: {
                    labels: {
                        color: "#919191"
                    },
                    markers: {
                        color: "#919191"
                    }
                }
            }
        },

        initSeries: function () {
            var series = this.series,
                i, currentSeries;

            for (i = 0; i < series.length; i++) {
                currentSeries = series[i];
                currentSeries.index = i;
            }
        },

        createPanes: function () {
            var plotArea = this,
                panes = [],
                paneOptions = plotArea.options.panes || [],
                i,
                panesLength = math.max(paneOptions.length, 1),
                currentPane;

            for (i = 0; i < panesLength; i++) {
                currentPane = new Pane(paneOptions[i]);
                currentPane.paneIndex = i;

                panes.push(currentPane);
                plotArea.append(currentPane);
            }

            plotArea.panes = panes;
        },

        destroy: function () {
            var plotArea = this,
                charts = plotArea.charts,
                axes = plotArea.axes,
                crosshairs = plotArea.crosshairs,
                i;

            for (i = 0; i < charts.length; i++) {
                charts[i].destroy();
            }

            for (i = 0; i < axes.length; i++) {
                axes[i].destroy();
            }

            for (i = 0; i < crosshairs.length; i++) {
                crosshairs[i].destroy();
            }

            ChartElement.fn.destroy.call(plotArea);
        },

        createCrosshairs: function (panes) {
            var plotArea = this,
                i, j, pane, axis, currentCrosshair;

            panes = panes || plotArea.panes;
            for (i = 0; i < panes.length; i++) {
                pane = panes[i];
                for (j = 0; j < pane.axes.length; j++) {
                    axis = pane.axes[j];
                    if (axis.options.crosshair && axis.options.crosshair.visible) {
                        currentCrosshair = new Crosshair(axis, axis.options.crosshair);

                        plotArea.crosshairs.push(currentCrosshair);
                        pane.content.append(currentCrosshair);
                    }
                }
            }
        },

        removeCrosshairs: function (pane) {
            var plotArea = this,
               crosshairs = plotArea.crosshairs,
               axes = pane.axes,
               i, j;

            for (i = crosshairs.length - 1; i >= 0; i--) {
                for (j = 0; j < axes.length; j++) {
                    if (crosshairs[i].axis === axes[j]) {
                        crosshairs.splice(i, 1);
                        break;
                    }
                }
            }
        },

        findPane: function (name) {
            var plotArea = this,
                panes = plotArea.panes,
                i, matchingPane;

            for (i = 0; i < panes.length; i++) {
                if (panes[i].options.name === name) {
                    matchingPane = panes[i];
                    break;
                }
            }

            return matchingPane || panes[0];
        },

        findPointPane: function (point) {
            var plotArea = this,
                panes = plotArea.panes,
                i, matchingPane;

            for (i = 0; i < panes.length; i++) {
                if (panes[i].box.containsPoint(point)) {
                    matchingPane = panes[i];
                    break;
                }
            }

            return matchingPane;
        },

        appendAxis: function (axis) {
            var plotArea = this,
                pane = plotArea.findPane(axis.options.pane);

            pane.appendAxis(axis);
            plotArea.axes.push(axis);
            axis.plotArea = plotArea;
        },

        removeAxis: function (axisToRemove) {
            var plotArea = this,
                i, axis,
                filteredAxes = [];

            for (i = 0; i < plotArea.axes.length; i++) {
                axis = plotArea.axes[i];
                if (axisToRemove !== axis) {
                    filteredAxes.push(axis);
                } else {
                    axis.destroy();
                }
            }

            plotArea.axes = filteredAxes;
        },

        appendChart: function (chart, pane) {
            var plotArea = this;

            plotArea.charts.push(chart);
            if (pane) {
                pane.appendChart(chart);
            } else {
                plotArea.append(chart);
            }
        },

        removeChart: function (chartToRemove) {
            var plotArea = this,
                i, chart,
                filteredCharts = [];

            for (i = 0; i < plotArea.charts.length; i++) {
                chart = plotArea.charts[i];
                if (chart !== chartToRemove) {
                    filteredCharts.push(chart);
                } else {
                    chart.destroy();
                }
            }

            plotArea.charts = filteredCharts;
        },

        addToLegend: function (series) {
            var count = series.length,
                data = [],
                i, currentSeries, text,
                legend = this.options.legend,
                labels = legend.labels || {},
                inactiveItems = legend.inactiveItems || {},
                inactiveItemsLabels = inactiveItems.labels || {},
                color, itemLabelOptions, markerColor,
                defaults, seriesVisible, labelTemplate;

            for (i = 0; i < count; i++) {
                currentSeries = series[i];
                seriesVisible = currentSeries.visible !== false;
                if (currentSeries.visibleInLegend === false) {
                    continue;
                }

                text = currentSeries.name || "";
                labelTemplate = seriesVisible ? labels.template :
                    (inactiveItemsLabels.template || labels.template);
                if (labelTemplate) {
                    text = template(labelTemplate)({
                        text: text,
                        series: currentSeries
                    });
                }

                color = currentSeries.color;
                defaults = currentSeries._defaults;
                if (isFn(color) && defaults) {
                    color = defaults.color;
                }

                if (seriesVisible) {
                    itemLabelOptions = {};
                    markerColor = color;
                } else {
                    itemLabelOptions = {
                        color: inactiveItemsLabels.color,
                        font: inactiveItemsLabels.font
                    };
                    markerColor = inactiveItems.markers.color;
                }

                if (text) {
                    data.push({
                        text: text,
                        labels: itemLabelOptions,
                        markerColor: markerColor,
                        series: currentSeries,
                        active: seriesVisible
                    });
                }
            }

            append(legend.items, data);
        },

        groupAxes: function (panes) {
            var xAxes = [],
                yAxes = [],
                paneAxes, axis, paneIx, axisIx;

            for (paneIx = 0; paneIx < panes.length; paneIx++) {
                paneAxes = panes[paneIx].axes;
                for (axisIx = 0; axisIx < paneAxes.length; axisIx++) {
                    axis = paneAxes[axisIx];
                    if (axis.options.vertical) {
                        yAxes.push(axis);
                    } else {
                        xAxes.push(axis);
                    }
                }
            }

            return { x: xAxes, y: yAxes, any: xAxes.concat(yAxes) };
        },

        groupSeriesByPane: function () {
            var plotArea = this,
                series = plotArea.series,
                seriesByPane = {},
                i, pane, currentSeries;

            for (i = 0; i < series.length; i++) {
                currentSeries = series[i];
                pane = plotArea.seriesPaneName(currentSeries);

                if (seriesByPane[pane]) {
                    seriesByPane[pane].push(currentSeries);
                } else {
                    seriesByPane[pane] = [currentSeries];
                }
            }

            return seriesByPane;
        },

        filterVisibleSeries: function (series) {
            var i, currentSeries,
                result = [];

            for (i = 0; i < series.length; i++) {
                currentSeries = series[i];
                if (currentSeries.visible !== false) {
                    result.push(currentSeries);
                }
            }

            return result;
        },

        reflow: function (targetBox) {
            var plotArea = this,
                options = plotArea.options.plotArea,
                panes = plotArea.panes,
                margin = getSpacing(options.margin);

            plotArea.box = targetBox.clone().unpad(margin);
            plotArea.reflowPanes();

            plotArea.reflowAxes(panes);
            plotArea.reflowCharts(panes);
        },

        redraw: function (panes) {
            var plotArea = this,
                i;

            panes = [].concat(panes);
            this.initSeries();

            for (i = 0; i < panes.length; i++) {
                plotArea.removeCrosshairs(panes[i]);
                panes[i].empty();
            }

            plotArea.render(panes);
            plotArea.reflowAxes(plotArea.panes);
            plotArea.reflowCharts(panes);

            plotArea.createCrosshairs(panes);

            for (i = 0; i < panes.length; i++) {
                panes[i].refresh();
            }
        },

        axisCrossingValues: function (axis, crossingAxes) {
            var options = axis.options,
                crossingValues = [].concat(
                    options.axisCrossingValues || options.axisCrossingValue
                ),
                valuesToAdd = crossingAxes.length - crossingValues.length,
                defaultValue = crossingValues[0] || 0,
                i;

            for (i = 0; i < valuesToAdd; i++) {
                crossingValues.push(defaultValue);
            }

            return crossingValues;
        },

        alignAxisTo: function (axis, targetAxis, crossingValue, targetCrossingValue) {
            var slot = axis.getSlot(crossingValue, crossingValue, true),
                slotEdge = axis.options.reverse ? 2 : 1,
                targetSlot = targetAxis.getSlot(targetCrossingValue, targetCrossingValue, true),
                targetEdge = targetAxis.options.reverse ? 2 : 1,
                axisBox = axis.box.translate(
                    targetSlot[X + targetEdge] - slot[X + slotEdge],
                    targetSlot[Y + targetEdge] - slot[Y + slotEdge]
                );

            if (axis.pane !== targetAxis.pane) {
                axisBox.translate(0, axis.pane.box.y1 - targetAxis.pane.box.y1);
            }

            axis.reflow(axisBox);
        },

        alignAxes: function (xAxes, yAxes) {
            var plotArea = this,
                xAnchor = xAxes[0],
                yAnchor = yAxes[0],
                xAnchorCrossings = plotArea.axisCrossingValues(xAnchor, yAxes),
                yAnchorCrossings = plotArea.axisCrossingValues(yAnchor, xAxes),
                leftAnchors = {},
                rightAnchors = {},
                topAnchors = {},
                bottomAnchors = {},
                pane, paneId, axis, i;

            for (i = 0; i < yAxes.length; i++) {
                axis = yAxes[i];
                pane = axis.pane;
                paneId = pane.id;
                plotArea.alignAxisTo(axis, xAnchor, yAnchorCrossings[i], xAnchorCrossings[i]);

                if (axis.options._overlap) {
                    continue;
                }

                if (round(axis.lineBox().x1) === round(xAnchor.lineBox().x1)) {
                    if (leftAnchors[paneId]) {
                        axis.reflow(axis.box
                            .alignTo(leftAnchors[paneId].box, LEFT)
                            .translate(-axis.options.margin, 0)
                        );
                    }

                    leftAnchors[paneId] = axis;
                }

                if (round(axis.lineBox().x2) === round(xAnchor.lineBox().x2)) {
                    if (!axis._mirrored) {
                        axis.options.labels.mirror = !axis.options.labels.mirror;
                        axis._mirrored = true;
                    }
                    plotArea.alignAxisTo(axis, xAnchor, yAnchorCrossings[i], xAnchorCrossings[i]);

                    if (rightAnchors[paneId]) {
                        axis.reflow(axis.box
                            .alignTo(rightAnchors[paneId].box, RIGHT)
                            .translate(axis.options.margin, 0)
                        );
                    }

                    rightAnchors[paneId] = axis;
                }

                if (i !== 0 && yAnchor.pane === axis.pane) {
                    axis.alignTo(yAnchor);
                }
            }

            for (i = 0; i < xAxes.length; i++) {
                axis = xAxes[i];
                pane = axis.pane;
                paneId = pane.id;
                plotArea.alignAxisTo(axis, yAnchor, xAnchorCrossings[i], yAnchorCrossings[i]);

                if (axis.options._overlap) {
                    continue;
                }

                if (round(axis.lineBox().y1) === round(yAnchor.lineBox().y1)) {
                    if (!axis._mirrored) {
                        axis.options.labels.mirror = !axis.options.labels.mirror;
                        axis._mirrored = true;
                    }
                    plotArea.alignAxisTo(axis, yAnchor, xAnchorCrossings[i], yAnchorCrossings[i]);

                    if (topAnchors[paneId]) {
                        axis.reflow(axis.box
                            .alignTo(topAnchors[paneId].box, TOP)
                            .translate(0, -axis.options.margin)
                        );
                    }

                    topAnchors[paneId] = axis;
                }

                if (round(axis.lineBox().y2, COORD_PRECISION) === round(yAnchor.lineBox().y2, COORD_PRECISION)) {
                    if (bottomAnchors[paneId]) {
                        axis.reflow(axis.box
                            .alignTo(bottomAnchors[paneId].box, BOTTOM)
                            .translate(0, axis.options.margin)
                        );
                    }

                    bottomAnchors[paneId] = axis;
                }

                if (i !== 0) {
                    axis.alignTo(xAnchor);
                }
            }
        },

        shrinkAxisWidth: function (panes) {
            var plotArea = this,
                axes = plotArea.groupAxes(panes).any,
                axisBox = axisGroupBox(axes),
                overflowX = 0,
                i, currentPane, currentAxis;

            for (i = 0; i < panes.length; i++) {
                currentPane = panes[i];

                if (currentPane.axes.length > 0) {
                    overflowX = math.max(
                        overflowX,
                        axisBox.width() - currentPane.contentBox.width()
                    );
                }
            }

            for (i = 0; i < axes.length; i++) {
                currentAxis = axes[i];

                if (!currentAxis.options.vertical) {
                    currentAxis.reflow(currentAxis.box.shrink(overflowX, 0));
                }
            }
        },

        shrinkAxisHeight: function (panes) {
            var i, currentPane, axes,
                overflowY, j, currentAxis;

            for (i = 0; i < panes.length; i++) {
                currentPane = panes[i];
                axes = currentPane.axes;
                overflowY = math.max(
                    0,
                    axisGroupBox(axes).height() - currentPane.contentBox.height()
                );

                for (j = 0; j < axes.length; j++) {
                    currentAxis = axes[j];

                    if (currentAxis.options.vertical) {
                        currentAxis.reflow(
                            currentAxis.box.shrink(0, overflowY)
                        );
                    }
                }
            }
        },

        fitAxes: function (panes) {
            var plotArea = this,
                axes = plotArea.groupAxes(panes).any,
                offsetX = 0,
                paneAxes, paneBox, axisBox, offsetY,
                currentPane, currentAxis, i, j;

            for (i = 0; i < panes.length; i++) {
                currentPane = panes[i];
                paneAxes = currentPane.axes;
                paneBox = currentPane.contentBox;

                if (paneAxes.length > 0) {
                    axisBox = axisGroupBox(paneAxes);

                    // OffsetX is calculated and applied globally
                    offsetX = math.max(offsetX, paneBox.x1 - axisBox.x1);

                    // OffsetY is calculated and applied per pane
                    offsetY = math.max(paneBox.y1 - axisBox.y1, paneBox.y2 - axisBox.y2);

                    for (j = 0; j < paneAxes.length; j++) {
                        currentAxis = paneAxes[j];

                        currentAxis.reflow(
                            currentAxis.box.translate(0, offsetY)
                        );
                    }
                }
            }

            for (i = 0; i < axes.length; i++) {
                currentAxis = axes[i];

                currentAxis.reflow(
                    currentAxis.box.translate(offsetX, 0)
                );
            }
        },

        reflowAxes: function (panes) {
            var plotArea = this,
                i,
                axes = plotArea.groupAxes(panes);

            for (i = 0; i < panes.length; i++) {
                plotArea.reflowPaneAxes(panes[i]);
            }

            if (axes.x.length > 0 && axes.y.length > 0) {
                plotArea.alignAxes(axes.x, axes.y);
                plotArea.shrinkAxisWidth(panes);
                plotArea.alignAxes(axes.x, axes.y);
                plotArea.shrinkAxisHeight(panes);
                plotArea.alignAxes(axes.x, axes.y);
                plotArea.fitAxes(panes);
            }
        },

        reflowPaneAxes: function (pane) {
            var axes = pane.axes,
                i,
                length = axes.length;

            if (length > 0) {
                for (i = 0; i < length; i++) {
                    axes[i].reflow(pane.contentBox);
                }
            }
        },

        reflowCharts: function (panes) {
            var plotArea = this,
                charts = plotArea.charts,
                count = charts.length,
                box = plotArea.box,
                chartPane, i;

            for (i = 0; i < count; i++) {
                chartPane = charts[i].pane;
                if (!chartPane || inArray(chartPane, panes)) {
                    charts[i].reflow(box);
                }
            }
        },

        reflowPanes: function () {
            var plotArea = this,
                box = plotArea.box,
                panes = plotArea.panes,
                panesLength = panes.length,
                i, currentPane, paneBox,
                remainingHeight = box.height(),
                remainingPanes = panesLength,
                autoHeightPanes = 0,
                top = box.y1,
                height, percents;

            for (i = 0; i < panesLength; i++) {
                currentPane = panes[i];
                height = currentPane.options.height;

                currentPane.options.width = box.width();

                if (!currentPane.options.height) {
                    autoHeightPanes++;
                } else {
                    if (height.indexOf && height.indexOf("%")) {
                        percents = parseInt(height, 10) / 100;
                        currentPane.options.height = percents * box.height();
                    }

                    currentPane.reflow(box.clone());

                    remainingHeight -= currentPane.options.height;
                }
            }

            for (i = 0; i < panesLength; i++) {
                currentPane = panes[i];

                if (!currentPane.options.height) {
                    currentPane.options.height = remainingHeight / autoHeightPanes;
                }
            }

            for (i = 0; i < panesLength; i++) {
                currentPane = panes[i];

                paneBox = box
                    .clone()
                    .move(box.x1, top);

                currentPane.reflow(paneBox);

                remainingPanes--;
                top += currentPane.options.height;
            }
        },

        backgroundBox: function () {
            var plotArea = this,
                axes = plotArea.axes,
                axesCount = axes.length,
                lineBox, box, i, j, axisA, axisB;

            for (i = 0; i < axesCount; i++) {
                axisA = axes[i];

                for (j = 0; j < axesCount; j++) {
                    axisB = axes[j];

                    if (axisA.options.vertical !== axisB.options.vertical) {
                        lineBox = axisA.lineBox().clone().wrap(axisB.lineBox());

                        if (!box) {
                            box = lineBox;
                        } else {
                            box = box.wrap(lineBox);
                        }
                    }
                }
            }

            return box || plotArea.box;
        },

        createVisual: function () {
            ChartElement.fn.createVisual.call(this);

            var bgBox = this.backgroundBox();
            var options = this.options.plotArea;
            var border = options.border || {};

            var bg = draw.Path.fromRect(bgBox.toRect(), {
                fill: {
                    color: options.background,
                    opacity: options.opacity
                },
                stroke: {
                    color: border.width ? border.color : "",
                    width: border.width
                },
                zIndex: -1
            });

            this.appendVisual(bg);
        },

        pointsByCategoryIndex: function (categoryIndex) {
            var charts = this.charts,
                result = [],
                i, j, points, point, chart;

            if (categoryIndex !== null) {
                for (i = 0; i < charts.length; i++) {
                    chart = charts[i];
                    if (chart.pane.options.name === "_navigator") {
                        continue;
                    }

                    points = charts[i].categoryPoints[categoryIndex];
                    if (points && points.length) {
                        for (j = 0; j < points.length; j++) {
                            point = points[j];
                            if (point && defined(point.value) && point.value !== null) {
                                result.push(point);
                            }
                        }
                    }
                }
            }

            return result;
        },

        pointsBySeriesIndex: function (seriesIndex) {
            var charts = this.charts,
                result = [],
                points, point, i, j, chart;

            for (i = 0; i < charts.length; i++) {
                chart = charts[i];
                points = chart.points;
                for (j = 0; j < points.length; j++) {
                    point = points[j];
                    if (point && point.options.index === seriesIndex) {
                        result.push(point);
                    }
                }
            }

            return result;
        },

        paneByPoint: function (point) {
            var plotArea = this,
                panes = plotArea.panes,
                pane, i;

            for (i = 0; i < panes.length; i++) {
                pane = panes[i];
                if (pane.box.containsPoint(point)) {
                    return pane;
                }
            }
        }
    });

    var CategoricalPlotArea = PlotAreaBase.extend({
        init: function (series, options) {
            var plotArea = this;

            plotArea.namedCategoryAxes = {};
            plotArea.namedValueAxes = {};
            plotArea.valueAxisRangeTracker = new AxisGroupRangeTracker();

            if (series.length > 0) {
                plotArea.invertAxes = inArray(
                    series[0].type, [BAR, BULLET, VERTICAL_LINE, VERTICAL_AREA, RANGE_BAR, HORIZONTAL_WATERFALL]
                );

                for (var i = 0; i < series.length; i++) {
                    var stack = series[i].stack;
                    if (stack && stack.type === "100%") {
                        plotArea.stack100 = true;
                        break;
                    }
                }
            }

            PlotAreaBase.fn.init.call(plotArea, series, options);
        },

        options: {
            categoryAxis: {
                categories: []
            },
            valueAxis: {}
        },

        render: function (panes) {
            var plotArea = this;

            panes = panes || plotArea.panes;

            plotArea.createCategoryAxes(panes);
            plotArea.aggregateCategories(panes);
            plotArea.createCharts(panes);
            plotArea.createValueAxes(panes);
        },

        removeAxis: function (axis) {
            var plotArea = this,
                axisName = axis.options.name;

            PlotAreaBase.fn.removeAxis.call(plotArea, axis);

            if (axis instanceof CategoryAxis) {
                delete plotArea.namedCategoryAxes[axisName];
            } else {
                plotArea.valueAxisRangeTracker.reset(axisName);
                delete plotArea.namedValueAxes[axisName];
            }

            if (axis === plotArea.categoryAxis) {
                delete plotArea.categoryAxis;
            }

            if (axis === plotArea.valueAxis) {
                delete plotArea.valueAxis;
            }
        },

        createCharts: function (panes) {
            var plotArea = this,
                seriesByPane = plotArea.groupSeriesByPane(),
                i, pane, paneSeries, filteredSeries;

            for (i = 0; i < panes.length; i++) {
                pane = panes[i];
                paneSeries = seriesByPane[pane.options.name || "default"] || [];
                plotArea.addToLegend(paneSeries);
                filteredSeries = plotArea.filterVisibleSeries(paneSeries);

                if (!filteredSeries) {
                    continue;
                }

                plotArea.createAreaChart(
                    filterSeriesByType(filteredSeries, [AREA, VERTICAL_AREA]),
                    pane
                );

                plotArea.createBarChart(
                    filterSeriesByType(filteredSeries, [COLUMN, BAR]),
                    pane
                );

                plotArea.createRangeBarChart(
                    filterSeriesByType(filteredSeries, [RANGE_COLUMN, RANGE_BAR]),
                    pane
                );

                plotArea.createBulletChart(
                    filterSeriesByType(filteredSeries, [BULLET, VERTICAL_BULLET]),
                    pane
                );

                plotArea.createCandlestickChart(
                    filterSeriesByType(filteredSeries, CANDLESTICK),
                    pane
                );

                plotArea.createBoxPlotChart(
                    filterSeriesByType(filteredSeries, BOX_PLOT),
                    pane
                );

                plotArea.createOHLCChart(
                    filterSeriesByType(filteredSeries, OHLC),
                    pane
                );

                plotArea.createWaterfallChart(
                    filterSeriesByType(filteredSeries, [WATERFALL, HORIZONTAL_WATERFALL]),
                    pane
                );

                plotArea.createLineChart(
                    filterSeriesByType(filteredSeries, [LINE, VERTICAL_LINE]),
                    pane
                );
            }
        },

        aggregateCategories: function (panes) {
            var plotArea = this,
                series = plotArea.srcSeries || plotArea.series,
                processedSeries = [],
                i, currentSeries,
                categoryAxis, axisPane, dateAxis;

            for (i = 0; i < series.length; i++) {
                currentSeries = series[i];
                categoryAxis = plotArea.seriesCategoryAxis(currentSeries);
                axisPane = plotArea.findPane(categoryAxis.options.pane);
                dateAxis = equalsIgnoreCase(categoryAxis.options.type, DATE);

                if ((dateAxis || currentSeries.categoryField) && inArray(axisPane, panes)) {
                    currentSeries = plotArea.aggregateSeries(currentSeries, categoryAxis);
                }

                processedSeries.push(currentSeries);

            }

            plotArea.srcSeries = series;
            plotArea.series = processedSeries;
        },

        aggregateSeries: function (series, categoryAxis) {
            var axisOptions = categoryAxis.options,
                dateAxis = equalsIgnoreCase(categoryAxis.options.type, DATE),
                categories = axisOptions.categories,
                srcCategories = axisOptions.srcCategories || categories,
                srcData = series.data,
                srcPoints = [],
                range = categoryAxis.range(),
                result = deepExtend({}, series),
                aggregatorSeries = deepExtend({}, series),
                i, category, categoryIx,
                data,
                aggregator,
                getFn = getField;

            result.data = data = [];

            if (dateAxis) {
                getFn = getDateField;
            }

            for (i = 0; i < srcData.length; i++) {
                if (series.categoryField) {
                    category = getFn(series.categoryField, srcData[i]);
                } else {
                    category = srcCategories[i];
                }

                categoryIx = categoryAxis.categoryIndex(category, range);
                if (categoryIx > -1) {
                    srcPoints[categoryIx] = srcPoints[categoryIx] || [];
                    srcPoints[categoryIx].push(i);
                }
            }

            aggregator = new SeriesAggregator(
                aggregatorSeries, SeriesBinder.current, DefaultAggregates.current
            );

            for (i = 0; i < categories.length; i++) {
                data[i] = aggregator.aggregatePoints(
                    srcPoints[i], categories[i]
                );
            }

            return result;
        },

        appendChart: function (chart, pane) {
            var plotArea = this,
                series = chart.options.series,
                categoryAxis = plotArea.seriesCategoryAxis(series[0]),
                categories = categoryAxis.options.categories,
                categoriesToAdd = math.max(0, categoriesCount(series) - categories.length);

            while (categoriesToAdd--) {
                categories.push("");
            }

            plotArea.valueAxisRangeTracker.update(chart.valueAxisRanges);

            PlotAreaBase.fn.appendChart.call(plotArea, chart, pane);
        },

        // TODO: Refactor, optionally use series.pane option
        seriesPaneName: function (series) {
            var plotArea = this,
                options = plotArea.options,
                axisName = series.axis,
                axisOptions = [].concat(options.valueAxis),
                axis = $.grep(axisOptions, function (a) { return a.name === axisName; })[0],
                panes = options.panes || [{}],
                defaultPaneName = (panes[0] || {}).name || "default",
                paneName = (axis || {}).pane || defaultPaneName;

            return paneName;
        },

        seriesCategoryAxis: function (series) {
            var plotArea = this,
                axisName = series.categoryAxis,
                axis = axisName ?
                    plotArea.namedCategoryAxes[axisName] :
                    plotArea.categoryAxis;

            if (!axis) {
                throw new Error("Unable to locate category axis with name " + axisName);
            }

            return axis;
        },

        stackableChartOptions: function (firstSeries, pane) {
            var stack = firstSeries.stack,
                isStacked100 = stack && stack.type === "100%",
                clip;
            if (defined(pane.options.clip)) {
                clip = pane.options.clip;
            } else if (isStacked100) {
                clip = false;
            }
            return {
                isStacked: stack,
                isStacked100: isStacked100,
                clip: clip
            };
        },

        createBarChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                firstSeries = series[0],
                barChart = new BarChart(plotArea, extend({
                    series: series,
                    invertAxes: plotArea.invertAxes,
                    gap: firstSeries.gap,
                    spacing: firstSeries.spacing
                }, plotArea.stackableChartOptions(firstSeries, pane)));

            plotArea.appendChart(barChart, pane);
        },

        createRangeBarChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                firstSeries = series[0],
                rangeColumnChart = new RangeBarChart(plotArea, {
                    series: series,
                    invertAxes: plotArea.invertAxes,
                    gap: firstSeries.gap,
                    spacing: firstSeries.spacing
                });

            plotArea.appendChart(rangeColumnChart, pane);
        },

        createBulletChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                firstSeries = series[0],
                bulletChart = new BulletChart(plotArea, {
                    series: series,
                    invertAxes: plotArea.invertAxes,
                    gap: firstSeries.gap,
                    spacing: firstSeries.spacing,
                    clip: pane.options.clip
                });

            plotArea.appendChart(bulletChart, pane);
        },

        createLineChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                firstSeries = series[0],
                lineChart = new LineChart(plotArea, extend({
                    invertAxes: plotArea.invertAxes,
                    series: series
                }, plotArea.stackableChartOptions(firstSeries, pane)));

            plotArea.appendChart(lineChart, pane);
        },

        createAreaChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                firstSeries = series[0],
                areaChart = new AreaChart(plotArea, extend({
                    invertAxes: plotArea.invertAxes,
                    series: series
                }, plotArea.stackableChartOptions(firstSeries, pane)));

            plotArea.appendChart(areaChart, pane);
        },

        createOHLCChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                firstSeries = series[0],
                chart = new OHLCChart(plotArea, {
                    invertAxes: plotArea.invertAxes,
                    gap: firstSeries.gap,
                    series: series,
                    spacing: firstSeries.spacing,
                    clip: pane.options.clip
                });

            plotArea.appendChart(chart, pane);
        },

        createCandlestickChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                firstSeries = series[0],
                chart = new CandlestickChart(plotArea, {
                    invertAxes: plotArea.invertAxes,
                    gap: firstSeries.gap,
                    series: series,
                    spacing: firstSeries.spacing,
                    clip: pane.options.clip
                });

            plotArea.appendChart(chart, pane);
        },

        createBoxPlotChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                firstSeries = series[0],
                chart = new BoxPlotChart(plotArea, {
                    invertAxes: plotArea.invertAxes,
                    gap: firstSeries.gap,
                    series: series,
                    spacing: firstSeries.spacing,
                    clip: pane.options.clip
                });

            plotArea.appendChart(chart, pane);
        },

        createWaterfallChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                firstSeries = series[0],
                waterfallChart = new WaterfallChart(plotArea, {
                    series: series,
                    invertAxes: plotArea.invertAxes,
                    gap: firstSeries.gap,
                    spacing: firstSeries.spacing
                });

            plotArea.appendChart(waterfallChart, pane);
        },

        axisRequiresRounding: function (categoryAxisName, categoryAxisIndex) {
            var plotArea = this,
                centeredSeries = filterSeriesByType(plotArea.series, EQUALLY_SPACED_SERIES),
                seriesIx,
                seriesAxis;

            for (seriesIx = 0; seriesIx < centeredSeries.length; seriesIx++) {
                seriesAxis = centeredSeries[seriesIx].categoryAxis || "";
                if (seriesAxis === categoryAxisName || (!seriesAxis && categoryAxisIndex === 0)) {
                    return true;
                }
            }
        },

        createCategoryAxes: function (panes) {
            var plotArea = this,
                invertAxes = plotArea.invertAxes,
                definitions = [].concat(plotArea.options.categoryAxis),
                i, axisOptions, axisPane,
                categories, type, name,
                categoryAxis, axes = [],
                primaryAxis;

            for (i = 0; i < definitions.length; i++) {
                axisOptions = definitions[i];
                axisPane = plotArea.findPane(axisOptions.pane);

                if (inArray(axisPane, panes)) {
                    name = axisOptions.name;
                    categories = axisOptions.categories || [];
                    type = axisOptions.type || "";
                    axisOptions = deepExtend({
                        vertical: invertAxes,
                        axisCrossingValue: invertAxes ? MAX_VALUE : 0
                    }, axisOptions);

                    if (!defined(axisOptions.justified)) {
                        axisOptions.justified = plotArea.isJustified();
                    }

                    if (plotArea.axisRequiresRounding(name, i)) {
                        axisOptions.justified = false;
                        axisOptions.roundToBaseUnit = true;
                    }

                    if (isDateAxis(axisOptions, categories[0])) {
                        categoryAxis = new DateCategoryAxis(axisOptions);
                    } else {
                        categoryAxis = new CategoryAxis(axisOptions);
                    }

                    if (name) {
                        if (plotArea.namedCategoryAxes[name]) {
                            throw new Error(
                                "Category axis with name " + name + " is already defined"
                            );
                        }
                        plotArea.namedCategoryAxes[name] = categoryAxis;
                    }

                    categoryAxis.axisIndex = i;
                    axes.push(categoryAxis);
                    plotArea.appendAxis(categoryAxis);
                }
            }

            primaryAxis = plotArea.categoryAxis || axes[0];
            plotArea.categoryAxis = primaryAxis;

            if (invertAxes) {
                plotArea.axisY = primaryAxis;
            } else {
                plotArea.axisX = primaryAxis;
            }
        },

        isJustified: function () {
            var plotArea = this,
                series = plotArea.series,
                i, currentSeries;

            for (i = 0; i < series.length; i++) {
                currentSeries = series[i];
                if (!inArray(currentSeries.type, [AREA, VERTICAL_AREA])) {
                    return false;
                }
            }

            return true;
        },

        createValueAxes: function (panes) {
            var plotArea = this,
                tracker = plotArea.valueAxisRangeTracker,
                defaultRange = tracker.query(),
                definitions = [].concat(plotArea.options.valueAxis),
                invertAxes = plotArea.invertAxes,
                baseOptions = { vertical: !invertAxes },
                axisOptions, axisPane, valueAxis,
                primaryAxis, axes = [], range,
                axisType, defaultAxisRange,
                name, i;

            if (plotArea.stack100) {
                baseOptions.roundToMajorUnit = false;
                baseOptions.labels = { format: "P0" };
            }

            for (i = 0; i < definitions.length; i++) {
                axisOptions = definitions[i];
                axisPane = plotArea.findPane(axisOptions.pane);

                if (inArray(axisPane, panes)) {
                    name = axisOptions.name;
                    defaultAxisRange = equalsIgnoreCase(axisOptions.type, LOGARITHMIC) ? { min: 0.1, max: 1} : { min: 0, max: 1 };
                    range = tracker.query(name) || defaultRange || defaultAxisRange;

                    if (i === 0 && range && defaultRange) {
                        range.min = math.min(range.min, defaultRange.min);
                        range.max = math.max(range.max, defaultRange.max);
                    }

                    if (equalsIgnoreCase(axisOptions.type, LOGARITHMIC)) {
                        axisType = LogarithmicAxis;
                    } else {
                        axisType = NumericAxis;
                    }

                    valueAxis = new axisType(range.min, range.max,
                        deepExtend({}, baseOptions, axisOptions)
                    );

                    if (name) {
                        if (plotArea.namedValueAxes[name]) {
                            throw new Error(
                                "Value axis with name " + name + " is already defined"
                            );
                        }
                        plotArea.namedValueAxes[name] = valueAxis;
                    }
                    valueAxis.axisIndex = i;

                    axes.push(valueAxis);
                    plotArea.appendAxis(valueAxis);
                }
            }

            primaryAxis = plotArea.valueAxis || axes[0];
            plotArea.valueAxis = primaryAxis;

            if (invertAxes) {
                plotArea.axisX = primaryAxis;
            } else {
                plotArea.axisY = primaryAxis;
            }
        },

        click: function (chart, e) {
            var plotArea = this,
                coords = chart._eventCoordinates(e),
                point = new Point2D(coords.x, coords.y),
                pane = plotArea.pointPane(point),
                allAxes,
                i,
                axis,
                categories = [],
                values = [];

            if (!pane) {
                return;
            }

            allAxes = pane.axes;
            for (i = 0; i < allAxes.length; i++) {
                axis = allAxes[i];
                if (axis.getValue) {
                    appendIfNotNull(values, axis.getValue(point));
                } else {
                    appendIfNotNull(categories, axis.getCategory(point));
                }
            }

            if (categories.length === 0) {
                appendIfNotNull(
                    categories, plotArea.categoryAxis.getCategory(point)
                );
            }

            if (categories.length > 0 && values.length > 0) {
                chart.trigger(PLOT_AREA_CLICK, {
                    element: $(e.target),
                    originalEvent: e,
                    category: singleItemOrArray(categories),
                    value: singleItemOrArray(values)
                });
            }
        },

        pointPane: function (point) {
            var plotArea = this,
                panes = plotArea.panes,
                currentPane,
                i;

            for (i = 0; i < panes.length; i++) {
                currentPane = panes[i];
                if (currentPane.contentBox.containsPoint(point)) {
                    return currentPane;
                }
            }
        }
    });

    var AxisGroupRangeTracker = Class.extend({
        init: function () {
            var tracker = this;

            tracker.axisRanges = {};
        },

        update: function (chartAxisRanges) {
            var tracker = this,
                axisRanges = tracker.axisRanges,
                range,
                chartRange,
                axisName;

            for (axisName in chartAxisRanges) {
                range = axisRanges[axisName];
                chartRange = chartAxisRanges[axisName];
                axisRanges[axisName] = range =
                    range || { min: MAX_VALUE, max: MIN_VALUE };

                range.min = math.min(range.min, chartRange.min);
                range.max = math.max(range.max, chartRange.max);
            }
        },

        reset: function (axisName) {
            this.axisRanges[axisName] = undefined;
        },

        query: function (axisName) {
            return this.axisRanges[axisName];
        }
    });

    var XYPlotArea = PlotAreaBase.extend({
        init: function (series, options) {
            var plotArea = this;

            plotArea.namedXAxes = {};
            plotArea.namedYAxes = {};

            plotArea.xAxisRangeTracker = new AxisGroupRangeTracker();
            plotArea.yAxisRangeTracker = new AxisGroupRangeTracker();

            PlotAreaBase.fn.init.call(plotArea, series, options);
        },

        options: {
            xAxis: {},
            yAxis: {}
        },

        render: function (panes) {
            var plotArea = this,
                seriesByPane = plotArea.groupSeriesByPane(),
                i, pane, paneSeries, filteredSeries;

            panes = panes || plotArea.panes;

            for (i = 0; i < panes.length; i++) {
                pane = panes[i];
                paneSeries = seriesByPane[pane.options.name || "default"] || [];
                plotArea.addToLegend(paneSeries);
                filteredSeries = plotArea.filterVisibleSeries(paneSeries);

                if (!filteredSeries) {
                    continue;
                }

                plotArea.createScatterChart(
                    filterSeriesByType(filteredSeries, SCATTER),
                    pane
                );

                plotArea.createScatterLineChart(
                    filterSeriesByType(filteredSeries, SCATTER_LINE),
                    pane
                );

                plotArea.createBubbleChart(
                    filterSeriesByType(filteredSeries, BUBBLE),
                    pane
                );
            }

            plotArea.createAxes(panes);
        },

        appendChart: function (chart, pane) {
            var plotArea = this;

            plotArea.xAxisRangeTracker.update(chart.xAxisRanges);
            plotArea.yAxisRangeTracker.update(chart.yAxisRanges);

            PlotAreaBase.fn.appendChart.call(plotArea, chart, pane);
        },

        removeAxis: function (axis) {
            var plotArea = this,
                axisName = axis.options.name;

            PlotAreaBase.fn.removeAxis.call(plotArea, axis);

            if (axis.options.vertical) {
                plotArea.yAxisRangeTracker.reset(axisName);
                delete plotArea.namedYAxes[axisName];
            } else {
                plotArea.xAxisRangeTracker.reset(axisName);
                delete plotArea.namedXAxes[axisName];
            }

            if (axis === plotArea.axisX) {
                delete plotArea.axisX;
            }

            if (axis === plotArea.axisY) {
                delete plotArea.axisY;
            }
        },

        // TODO: Refactor, optionally use series.pane option
        seriesPaneName: function (series) {
            var plotArea = this,
                options = plotArea.options,
                xAxisName = series.xAxis,
                xAxisOptions = [].concat(options.xAxis),
                xAxis = $.grep(xAxisOptions, function (a) { return a.name === xAxisName; })[0],
                yAxisName = series.yAxis,
                yAxisOptions = [].concat(options.yAxis),
                yAxis = $.grep(yAxisOptions, function (a) { return a.name === yAxisName; })[0],
                panes = options.panes || [{}],
                defaultPaneName = panes[0].name || "default",
                paneName = (xAxis || {}).pane || (yAxis || {}).pane || defaultPaneName;

            return paneName;
        },

        createScatterChart: function (series, pane) {
            var plotArea = this;

            if (series.length > 0) {
                plotArea.appendChart(
                    new ScatterChart(plotArea, { series: series, clip: pane.options.clip }),
                    pane
                );
            }
        },

        createScatterLineChart: function (series, pane) {
            var plotArea = this;

            if (series.length > 0) {
                plotArea.appendChart(
                    new ScatterLineChart(plotArea, { series: series, clip: pane.options.clip }),
                    pane
                );
            }
        },

        createBubbleChart: function (series, pane) {
            var plotArea = this;

            if (series.length > 0) {
                plotArea.appendChart(
                    new BubbleChart(plotArea, { series: series, clip: pane.options.clip }),
                    pane
                );
            }
        },

        createXYAxis: function (options, vertical, axisIndex) {
            var plotArea = this,
                axisName = options.name,
                namedAxes = vertical ? plotArea.namedYAxes : plotArea.namedXAxes,
                tracker = vertical ? plotArea.yAxisRangeTracker : plotArea.xAxisRangeTracker,
                axisOptions = deepExtend({}, options, { vertical: vertical }),
                isLog = equalsIgnoreCase(axisOptions.type, LOGARITHMIC),
                defaultRange = tracker.query(),
                defaultAxisRange = isLog ? { min: 0.1, max: 1} : { min: 0, max: 1 },
                range = tracker.query(axisName) || defaultRange || defaultAxisRange,
                axis,
                axisType,
                seriesIx,
                series = plotArea.series,
                currentSeries,
                seriesAxisName,
                firstPointValue,
                typeSamples = [axisOptions.min, axisOptions.max],
                inferredDate,
                i;

            for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
                currentSeries = series[seriesIx];
                seriesAxisName = currentSeries[vertical ? "yAxis" : "xAxis"];
                if ((seriesAxisName == axisOptions.name) || (axisIndex === 0 && !seriesAxisName)) {
                    firstPointValue = SeriesBinder.current.bindPoint(currentSeries, 0).valueFields;
                    typeSamples.push(firstPointValue[vertical ? "y" : "x"]);

                    break;
                }
            }

            if (axisIndex === 0 && defaultRange) {
                range.min = math.min(range.min, defaultRange.min);
                range.max = math.max(range.max, defaultRange.max);
            }

            for (i = 0; i < typeSamples.length; i++) {
                if (typeSamples[i] instanceof Date) {
                    inferredDate = true;
                    break;
                }
            }

            if (equalsIgnoreCase(axisOptions.type, DATE) || (!axisOptions.type && inferredDate)) {
                axisType = DateValueAxis;
            } else if (isLog) {
                axisType = LogarithmicAxis;
            } else {
                axisType = NumericAxis;
            }

            axis = new axisType(range.min, range.max, axisOptions);

            if (axisName) {
                if (namedAxes[axisName]) {
                    throw new Error(
                        (vertical ? "Y" : "X") +
                        " axis with name " + axisName + " is already defined"
                    );
                }
                namedAxes[axisName] = axis;
            }

            plotArea.appendAxis(axis);

            return axis;
        },

        createAxes: function (panes) {
            var plotArea = this,
                options = plotArea.options,
                axisPane,
                xAxesOptions = [].concat(options.xAxis),
                xAxes = [],
                yAxesOptions = [].concat(options.yAxis),
                yAxes = [];

            each(xAxesOptions, function (i) {
                axisPane = plotArea.findPane(this.pane);
                if (inArray(axisPane, panes)) {
                    xAxes.push(plotArea.createXYAxis(this, false, i));
                }
            });

            each(yAxesOptions, function (i) {
                axisPane = plotArea.findPane(this.pane);
                if (inArray(axisPane, panes)) {
                    yAxes.push(plotArea.createXYAxis(this, true, i));
                }
            });

            plotArea.axisX = plotArea.axisX || xAxes[0];
            plotArea.axisY = plotArea.axisY || yAxes[0];
        },

        click: function (chart, e) {
            var plotArea = this,
                coords = chart._eventCoordinates(e),
                point = new Point2D(coords.x, coords.y),
                allAxes = plotArea.axes,
                i,
                length = allAxes.length,
                axis,
                xValues = [],
                yValues = [],
                currentValue,
                values;

            for (i = 0; i < length; i++) {
                axis = allAxes[i];
                values = axis.options.vertical ? yValues : xValues;
                currentValue = axis.getValue(point);
                if (currentValue !== null) {
                    values.push(currentValue);
                }
            }

            if (xValues.length > 0 && yValues.length > 0) {
                chart.trigger(PLOT_AREA_CLICK, {
                    element: $(e.target),
                    originalEvent: e,
                    x: singleItemOrArray(xValues),
                    y: singleItemOrArray(yValues)
                });
            }
        }
    });

    var PiePlotArea = PlotAreaBase.extend({
        render: function () {
            var plotArea = this,
                series = plotArea.series;

            plotArea.createPieChart(series);
        },

        createPieChart: function (series) {
            var plotArea = this,
                firstSeries = series[0],
                pieChart = new PieChart(plotArea, {
                    series: series,
                    padding: firstSeries.padding,
                    startAngle: firstSeries.startAngle,
                    connectors: firstSeries.connectors,
                    legend: plotArea.options.legend
                });

            plotArea.appendChart(pieChart);
        },

        appendChart: function (chart, pane) {
            PlotAreaBase.fn.appendChart.call(this, chart, pane);
            append(this.options.legend.items, chart.legendItems);
        }
    });

    var DonutPlotArea = PiePlotArea.extend({
        render: function () {
            var plotArea = this,
                series = plotArea.series;

            plotArea.createDonutChart(series);
        },

        createDonutChart: function (series) {
            var plotArea = this,
                firstSeries = series[0],
                donutChart = new DonutChart(plotArea, {
                    series: series,
                    padding: firstSeries.padding,
                    connectors: firstSeries.connectors,
                    legend: plotArea.options.legend
                });

            plotArea.appendChart(donutChart);
        }
    });

    var PieAnimation = draw.Animation.extend({
        options: {
            easing: "easeOutElastic",
            duration: INITIAL_ANIMATION_DURATION
        },

        setup: function () {
            this.element.transform(geom.transform()
                .scale(0, 0, this.options.center)
            );
        },

        step: function (pos) {
            this.element.transform(geom.transform()
                .scale(pos, pos, this.options.center)
            );
        }
    });
    draw.AnimationFactory.current.register(PIE, PieAnimation);

    var BubbleAnimation = draw.Animation.extend({
        options: {
            easing: "easeOutElastic"
        },

        setup: function () {
            var center = this.center = this.element.bbox().center();
            this.element.transform(geom.transform()
                .scale(0, 0, center)
            );
        },

        step: function (pos) {
            this.element.transform(geom.transform()
                .scale(pos, pos, this.center)
            );
        }
    });
    draw.AnimationFactory.current.register(BUBBLE, BubbleAnimation);

    var Highlight = Class.extend({
        init: function () {
            this._points = [];
        },

        destroy: function () {
            this._points = [];
        },

        show: function (points) {
            points = [].concat(points);
            this.hide();

            for (var i = 0; i < points.length; i++) {
                var point = points[i];
                if (point && point.toggleHighlight) {
                    point.toggleHighlight(true);
                    this._points.push(point);
                }
            }
        },

        hide: function () {
            var points = this._points;
            while (points.length) {
                points.pop().toggleHighlight(false);
            }
        },

        isHighlighted: function (element) {
            var points = this._points;

            for (var i = 0; i < points.length; i++) {
                var point = points[i];
                if (element == point) {
                    return true;
                }
            }

            return false;
        }
    });

    var BaseTooltip = Class.extend({
        init: function (chartElement, options) {
            var tooltip = this;

            tooltip.options = deepExtend({}, tooltip.options, options);

            tooltip.chartElement = chartElement;

            tooltip.template = BaseTooltip.template;
            if (!tooltip.template) {
                tooltip.template = BaseTooltip.template = renderTemplate(
                    "<div class='" + CSS_PREFIX + "tooltip " + CSS_PREFIX + "chart-tooltip' " +
                    "style='display:none; position: absolute; font: #= d.font #;" +
                    "border: #= d.border.width #px solid;" +
                    "opacity: #= d.opacity #; filter: alpha(opacity=#= d.opacity * 100 #);'>" +
                    "</div>"
                );
            }

            tooltip.element = $(tooltip.template(tooltip.options));
            tooltip.move = proxy(tooltip.move, tooltip);
            tooltip._mouseleave = proxy(tooltip._mouseleave, tooltip);
        },

        destroy: function () {
            this._clearShowTimeout();

            if (this.element) {
                this.element.off(MOUSELEAVE_NS).remove();
                this.element = null;
            }
        },

        options: {
            border: {
                width: 1
            },
            opacity: 1,
            animation: {
                duration: TOOLTIP_ANIMATION_DURATION
            }
        },

        move: function () {
            var tooltip = this,
                options = tooltip.options,
                element = tooltip.element,
                offset;

            if (!tooltip.anchor) {
                return;
            }

            offset = tooltip._offset();
            if (!tooltip.visible) {
                element.css({ top: offset.top, left: offset.left });
            }

            tooltip._ensureElement(document.body);
            element
                .stop(true, true)
                .show()
                .animate({
                    left: offset.left,
                    top: offset.top
                }, options.animation.duration);

            tooltip.visible = true;
        },

        _clearShowTimeout: function () {
            if (this.showTimeout) {
                clearTimeout(this.showTimeout);
                this.showTimeout = null;
            }
        },

        _padding: function () {
            if (!this._chartPadding) {
                var chartElement = this.chartElement;
                this._chartPadding = {
                    top: parseInt(chartElement.css("paddingTop"), 10),
                    left: parseInt(chartElement.css("paddingLeft"), 10)
                };
            }

            return this._chartPadding;
        },

        _offset: function () {
            var tooltip = this,
                size = tooltip._measure(),
                anchor = tooltip.anchor,
                chartPadding = tooltip._padding(),
                chartOffset = tooltip.chartElement.offset(),
                top = round(anchor.y + chartPadding.top + chartOffset.top),
                left = round(anchor.x + chartPadding.left + chartOffset.left),
                zoomLevel = kendo.support.zoomLevel(),
                viewport = $(window),
                scrollTop = window.pageYOffset || document.documentElement.scrollTop || 0,
                scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || 0;

            top += tooltip._fit(top - scrollTop, size.height, viewport.outerHeight() / zoomLevel);
            left += tooltip._fit(left - scrollLeft, size.width, viewport.outerWidth() / zoomLevel);

            return {
                top: top,
                left: left
            };
        },

        setStyle: function (options, point) {
            var background = options.background;
            var border = options.border.color;

            if (point) {
                var pointColor = point.color || point.options.color;
                background = valueOrDefault(background, pointColor);
                border = valueOrDefault(border, pointColor);
            }

            if (!defined(options.color)) {
                var brightness = new Color(background).percBrightness();

                this.element.toggleClass(
                    CSS_PREFIX + TOOLTIP_INVERSE,
                    brightness > 180
                );
            }

            this.element.css({
                backgroundColor: background,
                borderColor: border,
                font: options.font,
                color: options.color,
                opacity: options.opacity,
                borderWidth: options.border.width
            });
        },

        show: function () {
            this._clearShowTimeout();
            this.showTimeout = setTimeout(this.move, TOOLTIP_SHOW_DELAY);
        },

        hide: function () {
            var tooltip = this;

            clearTimeout(tooltip.showTimeout);
            tooltip._hideElement();

            if (tooltip.visible) {
                tooltip.point = null;
                tooltip.visible = false;
                tooltip.index = null;
            }
        },

        _measure: function () {
            this._ensureElement();

            var size = {
                width: this.element.outerWidth(),
                height: this.element.outerHeight()
            };

            return size;
        },

        _ensureElement: function () {
            if (this.element) {
                this.element
                    .appendTo(document.body)
                    .on(MOUSELEAVE_NS, this._mouseleave);
            }
        },

        _mouseleave: function (e) {
            var target = e.relatedTarget;
            var chart = this.chartElement[0];
            if (target && target !== chart && !$.contains(chart, target)) {
                this.hide();
            }
        },

        _hideElement: function () {
            if (this.element) {
                this.element.fadeOut({
                    always: function () {
                        $(this).off(MOUSELEAVE_NS).remove();
                    }
                });
            }
        },

        _pointContent: function (point) {
            var tooltip = this,
                options = deepExtend({}, tooltip.options, point.options.tooltip),
                content, tooltipTemplate;

            if (defined(point.value)) {
                content = point.value.toString();
            }

            if (options.template) {
                tooltipTemplate = template(options.template);
                content = tooltipTemplate({
                    value: point.value,
                    category: point.category,
                    series: point.series,
                    dataItem: point.dataItem,
                    percentage: point.percentage,
                    runningTotal: point.runningTotal,
                    total: point.total,
                    low: point.low,
                    high: point.high,
                    xLow: point.xLow,
                    xHigh: point.xHigh,
                    yLow: point.yLow,
                    yHigh: point.yHigh
                });
            } else if (options.format) {
                content = point.formatValue(options.format);
            }

            return content;
        },

        _pointAnchor: function (point) {
            var size = this._measure();

            return point.tooltipAnchor(size.width, size.height);
        },

        _fit: function (offset, size, viewPortSize) {
            var output = 0;

            if (offset + size > viewPortSize) {
                output = viewPortSize - (offset + size);
            }

            if (offset < 0) {
                output = -offset;
            }

            return output;
        }
    });

    var Tooltip = BaseTooltip.extend({
        show: function (point) {
            var tooltip = this,
                options = deepExtend({}, tooltip.options, point.options.tooltip);

            if (!point || !point.tooltipAnchor || !tooltip.element) {
                return;
            }

            tooltip.element.html(tooltip._pointContent(point));
            tooltip.anchor = tooltip._pointAnchor(point);

            if (tooltip.anchor) {
                tooltip.setStyle(options, point);
                BaseTooltip.fn.show.call(tooltip, point);
            } else {
                tooltip.hide();
            }
        }
    });

    var SharedTooltip = BaseTooltip.extend({
        init: function (element, plotArea, options) {
            var tooltip = this;

            BaseTooltip.fn.init.call(tooltip, element, options);

            tooltip.plotArea = plotArea;
        },

        options: {
            sharedTemplate:
                "<table>" +
                "<th colspan='2'>#= categoryText #</th>" +
                "# for(var i = 0; i < points.length; i++) { #" +
                "# var point = points[i]; #" +
                "<tr>" +
                    "# if(point.series.name) { # " +
                        "<td> #= point.series.name #:</td>" +
                    "# } #" +
                    "<td>#= content(point) #</td>" +
                "</tr>" +
                "# } #" +
                "</table>",
            categoryFormat: "{0:d}"
        },

        showAt: function (points, coords) {
            var tooltip = this,
                options = tooltip.options,
                plotArea = tooltip.plotArea,
                axis = plotArea.categoryAxis,
                index = axis.pointCategoryIndex(coords),
                category = axis.getCategory(coords),
                slot = axis.getSlot(index),
                content;

            points = $.grep(points, function (p) {
                var tooltip = p.series.tooltip,
                    excluded = tooltip && tooltip.visible === false;

                return !excluded;
            });

            if (points.length > 0) {
                content = tooltip._content(points, category);
                tooltip.element.html(content);
                tooltip.anchor = tooltip._slotAnchor(coords, slot);
                tooltip.setStyle(options, points[0]);

                BaseTooltip.fn.show.call(tooltip);
            }
        },

        _slotAnchor: function (point, slot) {
            var tooltip = this,
                plotArea = tooltip.plotArea,
                axis = plotArea.categoryAxis,
                anchor,
                size = this._measure(),
                hCenter = point.y - size.height / 2;

            if (axis.options.vertical) {
                anchor = Point2D(point.x, hCenter);
            } else {
                anchor = Point2D(slot.center().x, hCenter);
            }

            return anchor;
        },

        _content: function (points, category) {
            var tooltip = this,
                template,
                content;

            template = kendo.template(tooltip.options.sharedTemplate);
            content = template({
                points: points,
                category: category,
                categoryText: autoFormat(tooltip.options.categoryFormat, category),
                content: tooltip._pointContent
            });

            return content;
        }
    });

    var Crosshair = ChartElement.extend({
        init: function (axis, options) {
            ChartElement.fn.init.call(this, options);

            this.axis = axis;
            this.stickyMode = axis instanceof CategoryAxis;
        },

        options: {
            color: BLACK,
            width: 1,
            zIndex: -1,
            tooltip: {
                visible: false
            }
        },

        showAt: function (point) {
            var crosshair = this;

            this.point = point;
            this.moveLine();
            this.line.visible(true);

            var tooltipOptions = this.options.tooltip;
            if (tooltipOptions.visible) {
                if (!this.tooltip) {
                    this.tooltip = new CrosshairTooltip(this,
                        deepExtend({}, tooltipOptions, { stickyMode: this.stickyMode })
                    );
                }
                this.tooltip.showAt(point);
            }
        },

        hide: function () {
            this.line.visible(false);

            if (this.tooltip) {
                this.tooltip.hide();
            }
        },

        moveLine: function () {
            var crosshair = this,
                axis = crosshair.axis,
                vertical = axis.options.vertical,
                box = crosshair.getBox(),
                point = crosshair.point,
                dim = vertical ? Y : X,
                slot, lineStart, lineEnd;

            lineStart = new geom.Point(box.x1, box.y1);
            if (vertical) {
                lineEnd = new geom.Point(box.x2, box.y1);
            } else {
                lineEnd = new geom.Point(box.x1, box.y2);
            }

            if (point) {
                if (crosshair.stickyMode) {
                    slot = axis.getSlot(axis.pointCategoryIndex(point));
                    lineStart[dim] = lineEnd[dim] = slot.center()[dim];
                } else {
                    lineStart[dim] = lineEnd[dim] = point[dim];
                }
            }

            crosshair.box = box;

            this.line.moveTo(lineStart).lineTo(lineEnd);
        },

        getBox: function () {
            var crosshair = this,
                axis = crosshair.axis,
                axes = axis.pane.axes,
                length = axes.length,
                vertical = axis.options.vertical,
                box = axis.lineBox().clone(),
                dim = vertical ? X : Y,
                axisLineBox, currentAxis, i;

            for (i = 0; i < length; i++) {
                currentAxis = axes[i];
                if (currentAxis.options.vertical != vertical) {
                    if (!axisLineBox) {
                        axisLineBox = currentAxis.lineBox().clone();
                    } else {
                        axisLineBox.wrap(currentAxis.lineBox());
                    }
                }
            }

            box[dim + 1] = axisLineBox[dim + 1];
            box[dim + 2] = axisLineBox[dim + 2];

            return box;
        },

        createVisual: function () {
            ChartElement.fn.createVisual.call(this);

            var options = this.options;
            this.line = new draw.Path({
                stroke: {
                    color: options.color,
                    width: options.width,
                    opacity: options.opacity,
                    dashType: options.dashType
                },
                visible: false
            });

            this.moveLine();
            this.visual.append(this.line);
        },

        destroy: function () {
            var crosshair = this;
            if (crosshair.tooltip) {
                crosshair.tooltip.destroy();
            }

            ChartElement.fn.destroy.call(crosshair);
        }
    });

    var CrosshairTooltip = BaseTooltip.extend({
        init: function (crosshair, options) {
            var tooltip = this,
                chartElement = crosshair.axis.getRoot().chart.element;

            tooltip.crosshair = crosshair;

            BaseTooltip.fn.init.call(tooltip, chartElement, deepExtend({},
                tooltip.options, {
                    background: crosshair.axis.plotArea.options.seriesColors[0]
                },
                options));

            tooltip.setStyle(tooltip.options);
        },

        options: {
            padding: 10
        },

        showAt: function (point) {
            var tooltip = this,
                element = tooltip.element;

            if (element) {
                tooltip.point = point;
                tooltip.element.html(tooltip.content(point));
                tooltip.anchor = tooltip.getAnchor();

                tooltip.move();
            }
        },

        move: function () {
            var tooltip = this,
                element = tooltip.element,
                offset = tooltip._offset();

            tooltip._ensureElement();
            element.css({ top: offset.top, left: offset.left }).show();
        },

        content: function (point) {
            var tooltip = this,
                options = tooltip.options,
                axis = tooltip.crosshair.axis,
                axisOptions = axis.options,
                content, value, tooltipTemplate;

            value = content = axis[options.stickyMode ? "getCategory" : "getValue"](point);

            if (options.template) {
                tooltipTemplate = template(options.template);
                content = tooltipTemplate({
                    value: value
                });
            } else if (options.format) {
                content = autoFormat(options.format, value);
            } else {
                if (axisOptions.type === DATE) {
                    content = autoFormat(axisOptions.labels.dateFormats[axisOptions.baseUnit], value);
                }
            }

            return content;
        },

        getAnchor: function () {
            var tooltip = this,
                options = tooltip.options,
                position = options.position,
                crosshair = this.crosshair,
                vertical = !crosshair.axis.options.vertical,
                lineBox = crosshair.line.bbox(),
                size = this._measure(),
                halfWidth = size.width / 2,
                halfHeight = size.height / 2,
                padding = options.padding,
                anchor;

            if (vertical) {
                if (position === BOTTOM) {
                    anchor = lineBox.bottomLeft()
                        .translate(-halfWidth, padding);
                } else {
                    anchor = lineBox.topLeft()
                        .translate(-halfWidth, -size.height - padding);
                }
            } else {
                if (position === LEFT) {
                    anchor = lineBox.topLeft()
                        .translate(-size.width - padding, -halfHeight);
                } else {
                    anchor = lineBox.topRight()
                        .translate(padding, -halfHeight);
                }
            }

            return anchor;
        },

        hide: function () {
            this.element.hide();
            this.point = null;
        },

        destroy: function () {
            BaseTooltip.fn.destroy.call(this);

            this.point = null;
        }
    });

    var Aggregates = {
        min: function (values) {
            var min = MAX_VALUE,
                length = values.length,
                i, n;

            for (i = 0; i < length; i++) {
                n = values[i];
                if (isNumber(n)) {
                    min = math.min(min, n);
                }
            }

            return min === MAX_VALUE ? values[0] : min;
        },

        max: function (values) {
            var max = MIN_VALUE,
                length = values.length,
                i, n;

            for (i = 0; i < length; i++) {
                n = values[i];
                if (isNumber(n)) {
                    max = math.max(max, n);
                }
            }

            return max === MIN_VALUE ? values[0] : max;
        },

        sum: function (values) {
            var length = values.length,
                sum = 0,
                i, n;

            for (i = 0; i < length; i++) {
                n = values[i];
                if (isNumber(n)) {
                    sum += n;
                }
            }

            return sum;
        },

        sumOrNull: function (values) {
            var result = null;

            if (countNumbers(values)) {
                result = Aggregates.sum(values);
            }

            return result;
        },

        count: function (values) {
            var length = values.length,
                count = 0,
                i, val;

            for (i = 0; i < length; i++) {
                val = values[i];
                if (val !== null && defined(val)) {
                    count++;
                }
            }

            return count;
        },

        avg: function (values) {
            var result = values[0],
                count = countNumbers(values);

            if (count > 0) {
                result = Aggregates.sum(values) / count;
            }

            return result;
        },

        first: function (values) {
            var length = values.length,
                i,
                val;

            for (i = 0; i < length; i++) {
                val = values[i];
                if (val !== null && defined(val)) {
                    return val;
                }
            }

            return values[0];
        }
    };

    function DefaultAggregates() {
        this._defaults = {};
    }

    DefaultAggregates.prototype = {
        register: function (seriesTypes, aggregates) {
            for (var i = 0; i < seriesTypes.length; i++) {
                this._defaults[seriesTypes[i]] = aggregates;
            }
        },

        query: function (seriesType) {
            return this._defaults[seriesType];
        }
    };

    DefaultAggregates.current = new DefaultAggregates();

    var Selection = Observable.extend({
        init: function (chart, categoryAxis, options) {
            var that = this,
                chartElement = chart.element,
                categoryAxisLineBox = categoryAxis.lineBox(),
                valueAxis = that.getValueAxis(categoryAxis),
                valueAxisLineBox = valueAxis.lineBox(),
                selectorPrefix = "." + CSS_PREFIX,
                wrapper, padding;

            Observable.fn.init.call(that);

            that.options = deepExtend({}, that.options, options);
            options = that.options;
            that.chart = chart;
            that.chartElement = chartElement;
            that.categoryAxis = categoryAxis;
            that._dateAxis = that.categoryAxis instanceof DateCategoryAxis;
            that.valueAxis = valueAxis;

            if (that._dateAxis) {
                deepExtend(options, {
                    min: toDate(options.min),
                    max: toDate(options.max),
                    from: toDate(options.from),
                    to: toDate(options.to)
                });
            }

            that.template = Selection.template;
            if (!that.template) {
                that.template = Selection.template = renderTemplate(
                    "<div class='" + CSS_PREFIX + "selector' " +
                    "style='width: #= d.width #px; height: #= d.height #px;" +
                    " top: #= d.offset.top #px; left: #= d.offset.left #px;'>" +
                    "<div class='" + CSS_PREFIX + "mask'></div>" +
                    "<div class='" + CSS_PREFIX + "mask'></div>" +
                    "<div class='" + CSS_PREFIX + "selection'>" +
                    "<div class='" + CSS_PREFIX + "selection-bg'></div>" +
                    "<div class='" + CSS_PREFIX + "handle " + CSS_PREFIX + "leftHandle'><div></div></div>" +
                    "<div class='" + CSS_PREFIX + "handle " + CSS_PREFIX + "rightHandle'><div></div></div>" +
                    "</div></div>"
                );
            }

            padding = {
                left: parseInt(chartElement.css("paddingLeft"), 10),
                right: parseInt(chartElement.css("paddingTop"), 10)
            };

            that.options = deepExtend({}, {
                width: categoryAxisLineBox.width(),
                height: valueAxisLineBox.height(),
                padding: padding,
                offset: {
                    left: valueAxisLineBox.x2 + padding.left,
                    top: valueAxisLineBox.y1 + padding.right
                },
                from: options.min,
                to: options.max
            }, options);

            if (that.options.visible) {
                that.wrapper = wrapper = $(that.template(that.options)).appendTo(chartElement);

                that.selection = wrapper.find(selectorPrefix + "selection");
                that.leftMask = wrapper.find(selectorPrefix + "mask").first();
                that.rightMask = wrapper.find(selectorPrefix + "mask").last();
                that.leftHandle = wrapper.find(selectorPrefix + "leftHandle");
                that.rightHandle = wrapper.find(selectorPrefix + "rightHandle");
                that.options.selection = {
                    border: {
                        left: parseFloat(that.selection.css("border-left-width"), 10),
                        right: parseFloat(that.selection.css("border-right-width"), 10)
                    }
                };

                that.leftHandle.css("top", (that.selection.height() - that.leftHandle.height()) / 2);
                that.rightHandle.css("top", (that.selection.height() - that.rightHandle.height()) / 2);

                that.set(that._index(options.from), that._index(options.to));

                that.bind(that.events, that.options);
                that.wrapper[0].style.cssText = that.wrapper[0].style.cssText;

                that.wrapper.on(MOUSEWHEEL_NS, proxy(that._mousewheel, that));

                if (kendo.UserEvents) {
                    that.userEvents = new kendo.UserEvents(that.wrapper, {
                        global: true,
                        stopPropagation: true,
                        multiTouch: true,
                        start: proxy(that._start, that),
                        move: proxy(that._move, that),
                        end: proxy(that._end, that),
                        tap: proxy(that._tap, that),
                        gesturestart: proxy(that._gesturechange, that),
                        gesturechange: proxy(that._gesturechange, that)
                    });
                } else {
                    that.leftHandle.add(that.rightHandle).removeClass(CSS_PREFIX + "handle");
                }
            }
        },

        events: [
            SELECT_START,
            SELECT,
            SELECT_END
        ],

        options: {
            visible: true,
            mousewheel: {
                zoom: BOTH
            },
            min: MIN_VALUE,
            max: MAX_VALUE
        },

        destroy: function () {
            var that = this,
                userEvents = that.userEvents;

            if (userEvents) {
                userEvents.destroy();
            }

            that._state = null;

            this.wrapper.remove();
        },

        _rangeEventArgs: function (range) {
            var that = this;

            return {
                axis: that.categoryAxis.options,
                from: that._value(range.from),
                to: that._value(range.to)
            };
        },

        _start: function (e) {
            var that = this,
                options = that.options,
                target = $(e.event.target),
                args;

            if (that._state || !target) {
                return;
            }

            that.chart._unsetActivePoint();
            that._state = {
                moveTarget: target.parents(".k-handle").add(target).first(),
                startLocation: e.x ? e.x.location : 0,
                range: {
                    from: that._index(options.from),
                    to: that._index(options.to)
                }
            };

            args = that._rangeEventArgs({
                from: that._index(options.from),
                to: that._index(options.to)
            });

            if (that.trigger(SELECT_START, args)) {
                that.userEvents.cancel();
                that._state = null;
            }
        },

        _move: function (e) {
            if (!this._state) {
                return;
            }

            var that = this,
                state = that._state,
                options = that.options,
                categories = that.categoryAxis.options.categories,
                from = that._index(options.from),
                to = that._index(options.to),
                min = that._index(options.min),
                max = that._index(options.max),
                delta = state.startLocation - e.x.location,
                range = state.range,
                oldRange = { from: range.from, to: range.to },
                span = range.to - range.from,
                target = state.moveTarget,
                scale = that.wrapper.width() / (categories.length - 1),
                offset = math.round(delta / scale);

            if (!target) {
                return;
            }

            e.preventDefault();

            if (target.is(".k-selection, .k-selection-bg")) {
                range.from = math.min(
                    math.max(min, from - offset),
                    max - span
                );
                range.to = math.min(
                    range.from + span,
                    max
                );
            } else if (target.is(".k-leftHandle")) {
                range.from = math.min(
                    math.max(min, from - offset),
                    max - 1
                );
                range.to = math.max(range.from + 1, range.to);
            } else if (target.is(".k-rightHandle")) {
                range.to = math.min(
                    math.max(min + 1, to - offset),
                    max
                );
                range.from = math.min(range.to - 1, range.from);
            }

            if (range.from !== oldRange.from || range.to !== oldRange.to) {
                that.move(range.from, range.to);
                that.trigger(SELECT, that._rangeEventArgs(range));
            }
        },

        _end: function () {
            var that = this,
                range = that._state.range;

            delete that._state;
            that.set(range.from, range.to);
            that.trigger(SELECT_END, that._rangeEventArgs(range));
        },

        _gesturechange: function (e) {
            if (!this._state) {
                return;
            }

            var that = this,
                chart = that.chart,
                state = that._state,
                options = that.options,
                categoryAxis = that.categoryAxis,
                range = state.range,
                p0 = chart._toModelCoordinates(e.touches[0].x.location).x,
                p1 = chart._toModelCoordinates(e.touches[1].x.location).x,
                left = math.min(p0, p1),
                right = math.max(p0, p1);

            e.preventDefault();
            state.moveTarget = null;

            range.from =
                categoryAxis.pointCategoryIndex(new dataviz.Point2D(left)) ||
                options.min;

            range.to =
                categoryAxis.pointCategoryIndex(new dataviz.Point2D(right)) ||
                options.max;

            that.move(range.from, range.to);
        },

        _tap: function (e) {
            var that = this,
                options = that.options,
                coords = that.chart._eventCoordinates(e),
                categoryAxis = that.categoryAxis,
                categoryIx = categoryAxis.pointCategoryIndex(
                    new dataviz.Point2D(coords.x, categoryAxis.box.y1)
                ),
                from = that._index(options.from),
                to = that._index(options.to),
                min = that._index(options.min),
                max = that._index(options.max),
                span = to - from,
                mid = from + span / 2,
                offset = math.round(mid - categoryIx),
                range = {},
                rightClick = e.event.which === 3;

            if (that._state || rightClick) {
                return;
            }

            e.preventDefault();
            that.chart._unsetActivePoint();

            if (!categoryAxis.options.justified) {
                offset--;
            }

            range.from = math.min(
                math.max(min, from - offset),
                max - span
            );

            range.to = math.min(range.from + span, max);

            that._start(e);
            if (that._state) {
                that._state.range = range;
                that.trigger(SELECT, that._rangeEventArgs(range));
                that._end();
            }
        },

        _mousewheel: function (e) {
            var that = this,
                options = that.options,
                delta = mwDelta(e);

            that._start({ event: { target: that.selection} });

            if (that._state) {
                var range = that._state.range;

                e.preventDefault();
                e.stopPropagation();

                if (math.abs(delta) > 1) {
                    delta *= ZOOM_ACCELERATION;
                }

                if (options.mousewheel.reverse) {
                    delta *= -1;
                }

                if (that.expand(delta)) {
                    that.trigger(SELECT, {
                        axis: that.categoryAxis.options,
                        delta: delta,
                        originalEvent: e,
                        from: that._value(range.from),
                        to: that._value(range.to)
                    });
                }

                if (that._mwTimeout) {
                    clearTimeout(that._mwTimeout);
                }

                that._mwTimeout = setTimeout(function () {
                    that._end();
                }, MOUSEWHEEL_DELAY);
            }
        },

        _index: function (value) {
            var that = this,
                categoryAxis = that.categoryAxis,
                categories = categoryAxis.options.categories,
                index = value;

            if (value instanceof Date) {
                index = lteDateIndex(value, categories);
                if (!categoryAxis.options.justified && value > last(categories)) {
                    index += 1;
                }
            }

            return index;
        },

        _value: function (index) {
            var that = this,
                categoryAxis = this.categoryAxis,
                categories = categoryAxis.options.categories,
                value = index;

            if (that._dateAxis) {
                if (index > categories.length - 1) {
                    value = that.options.max;
                } else {
                    value = categories[index];
                }
            }

            return value;
        },

        _slot: function (value) {
            var that = this,
                categoryAxis = this.categoryAxis;

            return categoryAxis.getSlot(that._index(value));
        },

        move: function (from, to) {
            var that = this,
                options = that.options,
                offset = options.offset,
                padding = options.padding,
                border = options.selection.border,
                leftMaskWidth,
                rightMaskWidth,
                box,
                distance;

            box = that._slot(from);
            leftMaskWidth = round(box.x1 - offset.left + padding.left);
            that.leftMask.width(leftMaskWidth);
            that.selection.css("left", leftMaskWidth);

            box = that._slot(to);
            rightMaskWidth = round(options.width - (box.x1 - offset.left + padding.left));
            that.rightMask.width(rightMaskWidth);
            distance = options.width - rightMaskWidth;
            if (distance != options.width) {
                distance += border.right;
            }

            that.rightMask.css("left", distance);
            that.selection.width(math.max(
                options.width - (leftMaskWidth + rightMaskWidth) - border.right,
                0
            ));
        },

        set: function (from, to) {
            var that = this,
                options = that.options,
                min = that._index(options.min),
                max = that._index(options.max);

            from = limitValue(that._index(from), min, max);
            to = limitValue(that._index(to), from + 1, max);

            if (options.visible) {
                that.move(from, to);
            }

            options.from = that._value(from);
            options.to = that._value(to);
        },

        expand: function (delta) {
            var that = this,
                options = that.options,
                min = that._index(options.min),
                max = that._index(options.max),
                zDir = options.mousewheel.zoom,
                from = that._index(options.from),
                to = that._index(options.to),
                range = { from: from, to: to },
                oldRange = deepExtend({}, range);

            if (that._state) {
                range = that._state.range;
            }

            if (zDir !== RIGHT) {
                range.from = limitValue(
                    limitValue(from - delta, 0, to - 1),
                    min, max
                );
            }

            if (zDir !== LEFT) {
                range.to = limitValue(
                    limitValue(to + delta, range.from + 1, max),
                    min,
                    max
                 );
            }

            if (range.from !== oldRange.from || range.to !== oldRange.to) {
                that.set(range.from, range.to);
                return true;
            }
        },

        getValueAxis: function (categoryAxis) {
            var axes = categoryAxis.pane.axes,
                axesCount = axes.length,
                i, axis;

            for (i = 0; i < axesCount; i++) {
                axis = axes[i];

                if (axis.options.vertical !== categoryAxis.options.vertical) {
                    return axis;
                }
            }
        }
    });

    var SeriesAggregator = function (series, binder, defaultAggregates) {
        var sa = this,
            canonicalFields = binder.canonicalFields(series),
            valueFields = binder.valueFields(series),
            sourceFields = binder.sourceFields(series, canonicalFields),
            seriesFields = sa._seriesFields = [],
            defaults = defaultAggregates.query(series.type),
            rootAggregate = series.aggregate || defaults,
            i;

        sa._series = series;
        sa._binder = binder;

        for (i = 0; i < canonicalFields.length; i++) {
            var field = canonicalFields[i],
                fieldAggregate;

            if (typeof rootAggregate === OBJECT) {
                fieldAggregate = rootAggregate[field];
            } else if (i === 0 || inArray(field, valueFields)) {
                fieldAggregate = rootAggregate;
            } else {
                break;
            }

            if (fieldAggregate) {
                seriesFields.push({
                    canonicalName: field,
                    name: sourceFields[i],
                    transform: isFn(fieldAggregate) ?
                        fieldAggregate : Aggregates[fieldAggregate]
                });
            }
        }
    };

    SeriesAggregator.prototype = {
        aggregatePoints: function (srcPoints, group) {
            var sa = this,
                data = sa._bindPoints(srcPoints || []),
                series = sa._series,
                seriesFields = sa._seriesFields,
                i,
                field,
                srcValues,
                value,
                firstDataItem = data.dataItems[0],
                result = {};

            if (firstDataItem && !isNumber(firstDataItem) && !isArray(firstDataItem)) {
                var fn = function () { };
                fn.prototype = firstDataItem;
                result = new fn();
            }

            for (i = 0; i < seriesFields.length; i++) {
                field = seriesFields[i];
                srcValues = sa._bindField(data.values, field.canonicalName);
                value = field.transform(srcValues, series, data.dataItems, group);

                if (value !== null && typeof value === OBJECT && !defined(value.length)) {
                    result = value;
                    break;
                } else {
                    if (defined(value)) {
                        ensureTree(field.name, result);
                        kendo.setter(field.name)(result, value);
                    }
                }
            }

            return result;
        },

        _bindPoints: function (points) {
            var sa = this,
                binder = sa._binder,
                series = sa._series,
                values = [],
                dataItems = [],
                i,
                pointIx;

            for (i = 0; i < points.length; i++) {
                pointIx = points[i];

                values.push(binder.bindPoint(series, pointIx));
                dataItems.push(series.data[pointIx]);
            }

            return {
                values: values,
                dataItems: dataItems
            };
        },

        _bindField: function (data, field) {
            var values = [],
                count = data.length,
                i, item, value, valueFields;

            for (i = 0; i < count; i++) {
                item = data[i];
                valueFields = item.valueFields;

                if (defined(valueFields[field])) {
                    value = valueFields[field];
                } else {
                    value = item.fields[field];
                }

                values.push(value);
            }

            return values;
        }
    };

    function intersection(a1, a2, b1, b2) {
        var result,
            ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
            u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y),
            ua;

        if (u_b !== 0) {
            ua = (ua_t / u_b);

            result = new Point2D(
                a1.x + ua * (a2.x - a1.x),
                a1.y + ua * (a2.y - a1.y)
            );
        }

        return result;
    }

    function applySeriesDefaults(options, themeOptions) {
        var series = options.series,
            i,
            seriesLength = series.length,
            seriesType,
            seriesDefaults = options.seriesDefaults,
            commonDefaults = deepExtend({}, options.seriesDefaults),
            themeSeriesDefaults = themeOptions ? deepExtend({}, themeOptions.seriesDefaults) : {},
            commonThemeDefaults = deepExtend({}, themeSeriesDefaults);

        cleanupNestedSeriesDefaults(commonDefaults);
        cleanupNestedSeriesDefaults(commonThemeDefaults);

        for (i = 0; i < seriesLength; i++) {

            seriesType = series[i].type || options.seriesDefaults.type;

            var baseOptions = deepExtend(
                { data: [] },
                commonThemeDefaults,
                themeSeriesDefaults[seriesType],
                { tooltip: options.tooltip },
                commonDefaults,
                seriesDefaults[seriesType]
            );

            series[i]._defaults = baseOptions;
            series[i] = deepExtend({}, baseOptions, series[i]);
        }
    }

    function cleanupNestedSeriesDefaults(seriesDefaults) {
        delete seriesDefaults.bar;
        delete seriesDefaults.column;
        delete seriesDefaults.rangeColumn;
        delete seriesDefaults.line;
        delete seriesDefaults.verticalLine;
        delete seriesDefaults.pie;
        delete seriesDefaults.donut;
        delete seriesDefaults.area;
        delete seriesDefaults.verticalArea;
        delete seriesDefaults.scatter;
        delete seriesDefaults.scatterLine;
        delete seriesDefaults.bubble;
        delete seriesDefaults.candlestick;
        delete seriesDefaults.ohlc;
        delete seriesDefaults.boxPlot;
        delete seriesDefaults.bullet;
        delete seriesDefaults.verticalBullet;
        delete seriesDefaults.polarArea;
        delete seriesDefaults.polarLine;
        delete seriesDefaults.radarArea;
        delete seriesDefaults.radarLine;
        delete seriesDefaults.waterfall;
    }

    function applySeriesColors(options) {
        var series = options.series,
            colors = options.seriesColors || [],
            i,
            currentSeries,
            seriesColor,
            defaults;

        for (i = 0; i < series.length; i++) {
            currentSeries = series[i];
            seriesColor = colors[i % colors.length];
            currentSeries.color = currentSeries.color || seriesColor;

            defaults = currentSeries._defaults;
            if (defaults) {
                defaults.color = defaults.color || seriesColor;
            }
        }
    }

    function resolveAxisAliases(options) {
        var alias;

        each([CATEGORY, VALUE, X, Y], function () {
            alias = this + "Axes";
            if (options[alias]) {
                options[this + "Axis"] = options[alias];
                delete options[alias];
            }
        });
    }

    function applyAxisDefaults(options, themeOptions) {
        var themeAxisDefaults = ((themeOptions || {}).axisDefaults) || {};

        each([CATEGORY, VALUE, X, Y], function () {
            var axisName = this + "Axis",
                axes = [].concat(options[axisName]),
                axisDefaults = options.axisDefaults || {};

            axes = $.map(axes, function (axisOptions) {
                var axisColor = (axisOptions || {}).color;
                var result = deepExtend({},
                    themeAxisDefaults,
                    themeAxisDefaults[axisName],
                    axisDefaults,
                    axisDefaults[axisName],
                    {
                        line: { color: axisColor },
                        labels: { color: axisColor },
                        title: { color: axisColor }
                    },
                    axisOptions
                );

                delete result[axisName];

                return result;
            });

            options[axisName] = axes.length > 1 ? axes : axes[0];
        });
    }

    function categoriesCount(series) {
        var seriesCount = series.length,
            categories = 0,
            i;

        for (i = 0; i < seriesCount; i++) {
            categories = math.max(categories, series[i].data.length);
        }

        return categories;
    }

    function sqr(value) {
        return value * value;
    }

    extend($.easing, {
        easeOutElastic: function (n, d, first, diff) {
            var s = 1.70158,
                p = 0,
                a = diff;

            if (n === 0) {
                return first;
            }

            if (n === 1) {
                return first + diff;
            }

            if (!p) {
                p = 0.5;
            }

            if (a < math.abs(diff)) {
                a = diff;
                s = p / 4;
            } else {
                s = p / (2 * math.PI) * math.asin(diff / a);
            }

            return a * math.pow(2, -10 * n) *
                   math.sin((n * 1 - s) * (1.1 * math.PI) / p) +
                   diff + first;
        }
    });

    function getField(field, row) {
        if (row === null) {
            return row;
        }

        var get = getter(field, true);
        return get(row);
    }

    function getDateField(field, row) {
        if (row === null) {
            return row;
        }

        var key = "_date_" + field,
            value = row[key];

        if (!value) {
            value = toDate(getter(field, true)(row));
            row[key] = value;
        }

        return value;
    }

    function toDate(value) {
        var result,
            i;

        if (value instanceof Date) {
            result = value;
        } else if (typeof value === STRING) {
            result = kendo.parseDate(value) || new Date(value);
        } else if (value) {
            if (isArray(value)) {
                result = [];
                for (i = 0; i < value.length; i++) {
                    result.push(toDate(value[i]));
                }
            } else {
                result = new Date(value);
            }
        }

        return result;
    }

    function toTime(value) {
        if (isArray(value)) {
            return map(value, toTime);
        } else if (value) {
            return toDate(value).getTime();
        }
    }

    function addDuration(date, value, unit, weekStartDay) {
        var result = date,
            hours;

        if (date) {
            date = toDate(date);
            hours = date.getHours();

            if (unit === YEARS) {
                result = new Date(date.getFullYear() + value, 0, 1);
                kendo.date.adjustDST(result, 0);
            } else if (unit === MONTHS) {
                result = new Date(date.getFullYear(), date.getMonth() + value, 1);
                kendo.date.adjustDST(result, hours);
            } else if (unit === WEEKS) {
                result = addDuration(startOfWeek(date, weekStartDay), value * 7, DAYS);
                kendo.date.adjustDST(result, hours);
            } else if (unit === DAYS) {
                result = new Date(date.getFullYear(), date.getMonth(), date.getDate() + value);
                kendo.date.adjustDST(result, hours);
            } else if (unit === HOURS) {
                result = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours() + value);
                if (value > 0 && dateEquals(date, result)) {
                    result = addDuration(date, value + 1, unit, weekStartDay);
                }
            } else if (unit === MINUTES) {
                result = new Date(date.getTime() + value * TIME_PER_MINUTE);

                if (result.getSeconds() > 0) {
                    result.setSeconds(0);
                }
            } else if (unit === SECONDS) {
                result = new Date(date.getTime() + value * TIME_PER_SECOND);
            }

            if (result.getMilliseconds() > 0) {
                result.setMilliseconds(0);
            }
        }

        return result;
    }

    function startOfWeek(date, weekStartDay) {
        var day = date.getDay(),
            daysToSubtract = 0;

        if (!isNaN(day)) {
            weekStartDay = weekStartDay || 0;
            while (day !== weekStartDay) {
                if (day === 0) {
                    day = 6;
                } else {
                    day--;
                }

                daysToSubtract++;
            }
        }

        return addTicks(date, -daysToSubtract * TIME_PER_DAY);
    }

    function floorDate(date, unit, weekStartDay) {
        date = toDate(date);

        return addDuration(date, 0, unit, weekStartDay);
    }

    function ceilDate(date, unit, weekStartDay) {
        date = toDate(date);

        if (date && floorDate(date, unit, weekStartDay).getTime() === date.getTime()) {
            return date;
        }

        return addDuration(date, 1, unit, weekStartDay);
    }

    function dateDiff(a, b) {
        var diff = a.getTime() - b,
            offsetDiff = a.getTimezoneOffset() - b.getTimezoneOffset();

        return diff - (offsetDiff * TIME_PER_MINUTE);
    }

    function addTicks(date, ticks) {
        var tzOffsetBefore = date.getTimezoneOffset(),
            result = new Date(date.getTime() + ticks),
            tzOffsetDiff = result.getTimezoneOffset() - tzOffsetBefore;

        return new Date(result.getTime() + tzOffsetDiff * TIME_PER_MINUTE);
    }

    function duration(a, b, unit) {
        var diff;

        if (unit === YEARS) {
            diff = b.getFullYear() - a.getFullYear();
        } else if (unit === MONTHS) {
            diff = duration(a, b, YEARS) * 12 + b.getMonth() - a.getMonth();
        } else if (unit === DAYS) {
            diff = math.floor(dateDiff(b, a) / TIME_PER_DAY);
        } else {
            diff = math.floor((b - a) / TIME_PER_UNIT[unit]);
        }

        return diff;
    }

    function singleItemOrArray(array) {
        return array.length === 1 ? array[0] : array;
    }

    function axisGroupBox(axes) {
        var length = axes.length,
            box, i, axisBox;

        if (length > 0) {
            for (i = 0; i < length; i++) {
                axisBox = axes[i].box;

                if (!box) {
                    box = axisBox.clone();
                } else {
                    box.wrap(axisBox);
                }
            }
        }

        return box || Box2D();
    }

    function equalsIgnoreCase(a, b) {
        if (a && b) {
            return a.toLowerCase() === b.toLowerCase();
        }

        return a === b;
    }

    function dateEquals(a, b) {
        if (a && b) {
            return toTime(a) === toTime(b);
        }

        return a === b;
    }

    function lastValue(array) {
        var i = array.length,
            value;

        while (i--) {
            value = array[i];
            if (defined(value) && value !== null) {
                return value;
            }
        }
    }

    function appendIfNotNull(array, element) {
        if (element !== null) {
            array.push(element);
        }
    }

    function lteDateIndex(date, sortedDates) {
        var low = 0,
            high = sortedDates.length - 1,
            i,
            currentDate;

        while (low <= high) {
            i = math.floor((low + high) / 2);
            currentDate = sortedDates[i];

            if (currentDate < date) {
                low = i + 1;
                continue;
            }

            if (currentDate > date) {
                high = i - 1;
                continue;
            }

            while (dateEquals(sortedDates[i - 1], date)) {
                i--;
            }

            return i;
        }

        if (sortedDates[i] <= date) {
            return i;
        } else {
            return i - 1;
        }
    }

    function isNumber(val) {
        return typeof val === "number" && !isNaN(val);
    }

    function countNumbers(values) {
        var length = values.length,
            count = 0,
            i,
            num;

        for (i = 0; i < length; i++) {
            num = values[i];
            if (isNumber(num)) {
                count++;
            }
        }

        return count;
    }

    function areNumbers(values) {
        return countNumbers(values) === values.length;
    }

    function axisRanges(axes) {
        var i,
            axis,
            axisName,
            ranges = {};

        for (i = 0; i < axes.length; i++) {
            axis = axes[i];
            axisName = axis.options.name;
            if (axisName) {
                ranges[axisName] = axis.range();
            }
        }

        return ranges;
    }

    function evalOptions(options, context, state, dryRun) {
        var property,
            propValue,
            excluded,
            defaults,
            depth,
            needsEval = false;

        state = state || {};
        excluded = state.excluded = state.excluded || [];
        defaults = state.defaults = state.defaults || {};
        depth = state.depth = state.depth || 0;

        if (depth > MAX_EXPAND_DEPTH) {
            return;
        }

        for (property in options) {
            if (!inArray(property, state.excluded) && options.hasOwnProperty(property)) {
                propValue = options[property];
                if (isFn(propValue)) {
                    needsEval = true;
                    if (!dryRun) {
                        options[property] = valueOrDefault(propValue(context), defaults[property]);
                    }
                } else if (typeof propValue === OBJECT) {
                    state.defaults = defaults[property];
                    state.depth++;
                    needsEval = evalOptions(propValue, context, state, dryRun) || needsEval;
                    state.depth--;
                }
            }
        }

        return needsEval;
    }

    function groupSeries(series, data) {
        var result = [],
            nameTemplate,
            legacyTemplate = series.groupNameTemplate,
            groupIx,
            dataLength = data.length,
            seriesClone;

        if (defined(legacyTemplate)) {
            kendo.logToConsole(
                "'groupNameTemplate' is obsolete and will be removed in future versions. " +
                "Specify the group name template as 'series.name'"
            );

            if (legacyTemplate) {
                nameTemplate = template(legacyTemplate);
            }
        } else {
            nameTemplate = template(series.name || "");
            if (nameTemplate._slotCount === 0) {
                nameTemplate = template(defined(series.name) ?
                    "#= group.value #: #= series.name #" :
                    "#= group.value #"
                );
            }
        }

        for (groupIx = 0; groupIx < dataLength; groupIx++) {
            seriesClone = deepExtend({}, series);

            if (!isFn(seriesClone.color)) {
                seriesClone.color = undefined;
            }

            seriesClone._groupIx = groupIx;
            result.push(seriesClone);

            if (nameTemplate) {
                seriesClone.name = nameTemplate({
                    series: seriesClone, group: data[groupIx]
                });
            }
        }

        return result;
    }

    function filterSeriesByType(series, types) {
        var i, currentSeries,
             result = [];

        types = [].concat(types);
        for (i = 0; i < series.length; i++) {
            currentSeries = series[i];
            if (inArray(currentSeries.type, types)) {
                result.push(currentSeries);
            }
        }

        return result;
    }

    function indexOf(item, arr) {
        if (item instanceof Date) {
            for (var i = 0, length = arr.length; i < length; i++) {
                if (dateEquals(arr[i], item)) {
                    return i;
                }
            }

            return -1;
        } else {
            return $.inArray(item, arr);
        }
    }

    function sortDates(dates, comparer) {
        comparer = comparer || dateComparer;

        for (var i = 1, length = dates.length; i < length; i++) {
            if (comparer(dates[i], dates[i - 1]) < 0) {
                dates.sort(comparer);
                break;
            }
        }

        return dates;
    }

    // Will mutate srcDates, not cloned for performance
    function uniqueDates(srcDates, comparer) {
        var i,
            dates = sortDates(srcDates, comparer),
            length = dates.length,
            result = length > 0 ? [dates[0]] : [];

        comparer = comparer || dateComparer;

        for (i = 1; i < length; i++) {
            if (comparer(dates[i], last(result)) !== 0) {
                result.push(dates[i]);
            }
        }

        return result;
    }

    function isDateAxis(axisOptions, sampleCategory) {
        var type = axisOptions.type,
            dateCategory = sampleCategory instanceof Date;

        return (!type && dateCategory) || equalsIgnoreCase(type, DATE);
    }

    function transpose(rows) {
        var result = [],
            rowCount = rows.length,
            rowIx,
            row,
            colIx,
            colCount;

        for (rowIx = 0; rowIx < rowCount; rowIx++) {
            row = rows[rowIx];
            colCount = row.length;
            for (colIx = 0; colIx < colCount; colIx++) {
                result[colIx] = result[colIx] || [];
                result[colIx].push(row[colIx]);
            }
        }

        return result;
    }

    function ensureTree(fieldName, target) {
        if (fieldName.indexOf(".") > -1) {
            var parts = fieldName.split("."),
                path = "",
                val;

            while (parts.length > 1) {
                path += parts.shift();
                val = kendo.getter(path)(target) || {};
                kendo.setter(path)(target, val);
                path += ".";
            }
        }
    }

    function seriesTotal(series) {
        var data = series.data;
        var sum = 0;

        for (var i = 0; i < data.length; i++) {
            var pointData = SeriesBinder.current.bindPoint(series, i);
            var value = pointData.valueFields.value;

            if (typeof value === STRING) {
                value = parseFloat(value);
            }

            if (isNumber(value) && pointData.fields.visible !== false) {
                sum += math.abs(value);
            }
        }

        return sum;
    }

    function hasGradientOverlay(options) {
        var overlay = options.overlay;
        return overlay && overlay.gradient && overlay.gradient != "none";
    }

    // Exports ================================================================
    dataviz.ui.plugin(Chart);

    PlotAreaFactory.current.register(CategoricalPlotArea, [
        BAR, COLUMN, LINE, VERTICAL_LINE, AREA, VERTICAL_AREA,
        CANDLESTICK, OHLC, BULLET, VERTICAL_BULLET, BOX_PLOT,
        RANGE_COLUMN, RANGE_BAR, WATERFALL, HORIZONTAL_WATERFALL
    ]);

    PlotAreaFactory.current.register(XYPlotArea, [
        SCATTER, SCATTER_LINE, BUBBLE
    ]);

    PlotAreaFactory.current.register(PiePlotArea, [PIE]);
    PlotAreaFactory.current.register(DonutPlotArea, [DONUT]);

    SeriesBinder.current.register(
        [BAR, COLUMN, LINE, VERTICAL_LINE, AREA, VERTICAL_AREA],
        [VALUE], [CATEGORY, COLOR, NOTE_TEXT, ERROR_LOW_FIELD, ERROR_HIGH_FIELD]
    );

    SeriesBinder.current.register(
        [RANGE_COLUMN, RANGE_BAR],
        [FROM, TO], [CATEGORY, COLOR, NOTE_TEXT]
    );

    SeriesBinder.current.register(
        [WATERFALL, HORIZONTAL_WATERFALL],
        [VALUE], [CATEGORY, COLOR, NOTE_TEXT, SUMMARY_FIELD]
    );

    DefaultAggregates.current.register(
        [BAR, COLUMN, LINE, VERTICAL_LINE, AREA, VERTICAL_AREA, WATERFALL, HORIZONTAL_WATERFALL],
        { value: MAX, color: FIRST, noteText: FIRST, errorLow: MIN, errorHigh: MAX }
    );

    DefaultAggregates.current.register(
        [RANGE_COLUMN, RANGE_BAR],
        { from: MIN, to: MAX, color: FIRST, noteText: FIRST }
    );

    SeriesBinder.current.register(
        [SCATTER, SCATTER_LINE, BUBBLE],
        [X, Y], [COLOR, NOTE_TEXT, X_ERROR_LOW_FIELD, X_ERROR_HIGH_FIELD, Y_ERROR_LOW_FIELD, Y_ERROR_HIGH_FIELD]
    );

    SeriesBinder.current.register(
        [BUBBLE], [X, Y, "size"], [COLOR, CATEGORY, NOTE_TEXT]
    );

    SeriesBinder.current.register(
        [CANDLESTICK, OHLC],
        ["open", "high", "low", "close"], [CATEGORY, COLOR, "downColor", NOTE_TEXT]
    );

    DefaultAggregates.current.register(
        [CANDLESTICK, OHLC],
        { open: MAX, high: MAX, low: MIN, close: MAX,
            color: FIRST, downColor: FIRST, noteText: FIRST
        }
    );

    SeriesBinder.current.register(
        [BOX_PLOT],
        ["lower", "q1", "median", "q3", "upper", "mean", "outliers"], [CATEGORY, COLOR, NOTE_TEXT]
    );

    DefaultAggregates.current.register(
        [BOX_PLOT],
        { lower: MAX, q1: MAX, median: MAX, q3: MAX, upper: MAX, mean: MAX, outliers: FIRST,
            color: FIRST, noteText: FIRST
        }
    );

    SeriesBinder.current.register(
        [BULLET, VERTICAL_BULLET],
        ["current", "target"], [CATEGORY, COLOR, "visibleInLegend", NOTE_TEXT]
    );

    DefaultAggregates.current.register(
        [BULLET, VERTICAL_BULLET],
        { current: MAX, target: MAX, color: FIRST, noteText: FIRST }
    );

    SeriesBinder.current.register(
        [PIE, DONUT],
        [VALUE], [CATEGORY, COLOR, "explode", "visibleInLegend", "visible"]
    );

    deepExtend(dataviz, {
        EQUALLY_SPACED_SERIES: EQUALLY_SPACED_SERIES,

        Aggregates: Aggregates,
        AreaChart: AreaChart,
        AreaSegment: AreaSegment,
        AxisGroupRangeTracker: AxisGroupRangeTracker,
        Bar: Bar,
        BarChart: BarChart,
        BarLabel: BarLabel,
        BubbleChart: BubbleChart,
        BulletChart: BulletChart,
        CandlestickChart: CandlestickChart,
        Candlestick: Candlestick,
        CategoricalChart: CategoricalChart,
        CategoricalErrorBar: CategoricalErrorBar,
        CategoricalPlotArea: CategoricalPlotArea,
        CategoryAxis: CategoryAxis,
        ChartContainer: ChartContainer,
        ClusterLayout: ClusterLayout,
        Crosshair: Crosshair,
        CrosshairTooltip: CrosshairTooltip,
        DateCategoryAxis: DateCategoryAxis,
        DateValueAxis: DateValueAxis,
        DefaultAggregates: DefaultAggregates,
        DonutChart: DonutChart,
        DonutPlotArea: DonutPlotArea,
        DonutSegment: DonutSegment,
        ErrorBarBase: ErrorBarBase,
        ErrorRangeCalculator: ErrorRangeCalculator,
        Highlight: Highlight,
        SharedTooltip: SharedTooltip,
        Legend: Legend,
        LegendItem: LegendItem,
        LineChart: LineChart,
        LinePoint: LinePoint,
        LineSegment: LineSegment,
        Pane: Pane,
        PieAnimation: PieAnimation,
        PieChart: PieChart,
        PieChartMixin: PieChartMixin,
        PiePlotArea: PiePlotArea,
        PieSegment: PieSegment,
        PlotAreaBase: PlotAreaBase,
        PlotAreaFactory: PlotAreaFactory,
        PointEventsMixin: PointEventsMixin,
        RangeBar: RangeBar,
        RangeBarChart: RangeBarChart,
        ScatterChart: ScatterChart,
        ScatterErrorBar: ScatterErrorBar,
        ScatterLineChart: ScatterLineChart,
        Selection: Selection,
        SeriesAggregator: SeriesAggregator,
        SeriesBinder: SeriesBinder,
        ShapeElement: ShapeElement,
        SplineSegment: SplineSegment,
        SplineAreaSegment: SplineAreaSegment,
        StackWrap: StackWrap,
        Tooltip: Tooltip,
        OHLCChart: OHLCChart,
        OHLCPoint: OHLCPoint,
        WaterfallChart: WaterfallChart,
        WaterfallSegment: WaterfallSegment,
        XYPlotArea: XYPlotArea,

        addDuration: addDuration,
        areNumbers: areNumbers,
        axisGroupBox: axisGroupBox,
        categoriesCount: categoriesCount,
        ceilDate: ceilDate,
        countNumbers: countNumbers,
        duration: duration,
        ensureTree: ensureTree,
        indexOf: indexOf,
        isNumber: isNumber,
        floorDate: floorDate,
        filterSeriesByType: filterSeriesByType,
        lteDateIndex: lteDateIndex,
        evalOptions: evalOptions,
        seriesTotal: seriesTotal,
        singleItemOrArray: singleItemOrArray,
        sortDates: sortDates,
        startOfWeek: startOfWeek,
        transpose: transpose,
        toDate: toDate,
        toTime: toTime,
        uniqueDates: uniqueDates
    });

})(window.kendo.jQuery);





(function ($, undefined) {
    // Imports ================================================================
    var math = Math,

        kendo = window.kendo,
        deepExtend = kendo.deepExtend,

        util = kendo.util,
        append = util.append,

        draw = kendo.drawing,
        geom = kendo.geometry,
        dataviz = kendo.dataviz,
        AreaSegment = dataviz.AreaSegment,
        Axis = dataviz.Axis,
        AxisGroupRangeTracker = dataviz.AxisGroupRangeTracker,
        BarChart = dataviz.BarChart,
        Box2D = dataviz.Box2D,
        CategoryAxis = dataviz.CategoryAxis,
        CategoricalChart = dataviz.CategoricalChart,
        CategoricalPlotArea = dataviz.CategoricalPlotArea,
        ChartElement = dataviz.ChartElement,
        CurveProcessor = dataviz.CurveProcessor,
        DonutSegment = dataviz.DonutSegment,
        LineChart = dataviz.LineChart,
        LineSegment = dataviz.LineSegment,
        LogarithmicAxis = dataviz.LogarithmicAxis,
        NumericAxis = dataviz.NumericAxis,
        PlotAreaBase = dataviz.PlotAreaBase,
        PlotAreaFactory = dataviz.PlotAreaFactory,
        Point2D = dataviz.Point2D,
        Ring = dataviz.Ring,
        ScatterChart = dataviz.ScatterChart,
        ScatterLineChart = dataviz.ScatterLineChart,
        SeriesBinder = dataviz.SeriesBinder,
        ShapeBuilder = dataviz.ShapeBuilder,
        SplineSegment = dataviz.SplineSegment,
        SplineAreaSegment = dataviz.SplineAreaSegment,
        getSpacing = dataviz.getSpacing,
        filterSeriesByType = dataviz.filterSeriesByType,
        limitValue = util.limitValue,
        round = dataviz.round;

    // Constants ==============================================================
    var ARC = "arc",
        BLACK = "#000",
        COORD_PRECISION = dataviz.COORD_PRECISION,
        DEFAULT_PADDING = 0.15,
        DEG_TO_RAD = math.PI / 180,
        LOGARITHMIC = "log",
        PLOT_AREA_CLICK = "plotAreaClick",
        POLAR_AREA = "polarArea",
        POLAR_LINE = "polarLine",
        POLAR_SCATTER = "polarScatter",
        RADAR_AREA = "radarArea",
        RADAR_COLUMN = "radarColumn",
        RADAR_LINE = "radarLine",
        SMOOTH = "smooth",
        X = "x",
        Y = "y",
        ZERO = "zero",
        POLAR_CHARTS = [
            POLAR_AREA, POLAR_LINE, POLAR_SCATTER
        ],
        RADAR_CHARTS = [
            RADAR_AREA, RADAR_COLUMN, RADAR_LINE
        ];

    // Polar and radar charts =================================================
    var GridLinesMixin = {
        createGridLines: function (altAxis) {
            var axis = this,
                options = axis.options,
                radius = math.abs(axis.box.center().y - altAxis.lineBox().y1),
                majorAngles,
                minorAngles,
                skipMajor = false,
                gridLines = [];

            if (options.majorGridLines.visible) {
                majorAngles = axis.majorGridLineAngles(altAxis);
                skipMajor = true;

                gridLines = axis.renderGridLines(
                    majorAngles, radius, options.majorGridLines
                );
            }

            if (options.minorGridLines.visible) {
                minorAngles = axis.minorGridLineAngles(altAxis, skipMajor);

                append(gridLines, axis.renderGridLines(
                    minorAngles, radius, options.minorGridLines
                ));
            }

            return gridLines;
        },

        renderGridLines: function (angles, radius, options) {
            var style = {
                stroke: {
                    Width: options.width,
                    color: options.color,
                    dashType: options.dashType
                }
            };

            var center = this.box.center();
            var circle = new geom.Circle([center.x, center.y], radius);
            var container = this.gridLinesVisual();

            for (var i = 0; i < angles.length; i++) {
                var line = new draw.Path(style);

                line.moveTo(circle.center)
                    .lineTo(circle.pointAt(angles[i]));

                container.append(line);
            }

            return container.children;
        },

        gridLineAngles: function (altAxis, step, skipStep) {
            var axis = this,
                divs = axis.intervals(step, skipStep);

            return $.map(divs, function (d) {
                var alpha = axis.intervalAngle(d);

                if (!altAxis.options.visible || alpha !== 90) {
                    return alpha;
                }
            });
        }
    };

    var RadarCategoryAxis = CategoryAxis.extend({
        options: {
            startAngle: 90,
            labels: {
                margin: getSpacing(10)
            },
            majorGridLines: {
                visible: true
            },
            justified: true
        },

        range: function () {
            return { min: 0, max: this.options.categories.length };
        },

        reflow: function (box) {
            this.box = box;
            this.reflowLabels();
        },

        lineBox: function () {
            return this.box;
        },

        reflowLabels: function () {
            var axis = this,
                measureBox = new Box2D(),
                labels = axis.labels,
                labelBox,
                i;

            for (i = 0; i < labels.length; i++) {
                labels[i].reflow(measureBox);
                labelBox = labels[i].box;

                labels[i].reflow(axis.getSlot(i).adjacentBox(
                    0, labelBox.width(), labelBox.height()
                ));
            }
        },

        intervals: function (step, skipStep) {
            var axis = this,
                options = axis.options,
                categories = options.categories.length,
                angle = 0,
                skipAngle = 0,
                divCount = categories / step || 1,
                divAngle = 360 / divCount,
                divs = [],
                i;

            if (skipStep) {
                skipAngle = 360 / (categories / skipStep);
            }

            for (i = 0; i < divCount; i++) {
                angle = round(angle, COORD_PRECISION);

                if (angle % skipAngle !== 0) {
                    divs.push(angle % 360);
                }

                if (options.reverse) {
                    angle = 360 + angle - divAngle;
                } else {
                    angle += divAngle;
                }
            }

            return divs;
        },

        majorIntervals: function () {
            return this.intervals(1);
        },

        minorIntervals: function () {
            return this.intervals(0.5);
        },

        intervalAngle: function (interval) {
            return (360 + interval + this.options.startAngle) % 360;
        },

        majorAngles: function () {
            return $.map(this.majorIntervals(), $.proxy(this.intervalAngle, this));
        },

        createLine: function () {
            return [];
        },

        majorGridLineAngles: function (altAxis) {
            return this.gridLineAngles(altAxis, 1);
        },

        minorGridLineAngles: function (altAxis, skipMajor) {
            return this.gridLineAngles(altAxis, 0.5, skipMajor ? 1 : 0);
        },

        createPlotBands: function () {
            var axis = this,
                options = axis.options,
                plotBands = options.plotBands || [],
                i,
                band,
                slot,
                singleSlot,
                head,
                tail;

            var group = new draw.Group({
                zIndex: -1
            });

            for (i = 0; i < plotBands.length; i++) {
                band = plotBands[i];
                slot = axis.plotBandSlot(band);
                singleSlot = axis.getSlot(band.from);

                head = band.from - math.floor(band.from);
                slot.startAngle += head * singleSlot.angle;

                tail = math.ceil(band.to) - band.to;
                slot.angle -= (tail + head) * singleSlot.angle;

                var ring = ShapeBuilder.current.createRing(slot, {
                    fill: {
                        color: band.color,
                        opacity: band.opacity
                    },
                    stroke: {
                        opacity: band.opacity
                    }
                });
                group.append(ring);
            }

            axis.appendVisual(group);
        },

        plotBandSlot: function (band) {
            return this.getSlot(band.from, band.to - 1);
        },

        getSlot: function (from, to) {
            var axis = this,
                options = axis.options,
                justified = options.justified,
                box = axis.box,
                divs = axis.majorAngles(),
                totalDivs = divs.length,
                slots,
                slotAngle = 360 / totalDivs,
                slotStart,
                angle;

            if (options.reverse && !justified) {
                from = (from + 1) % totalDivs;
            }

            from = limitValue(math.floor(from), 0, totalDivs - 1);
            slotStart = divs[from];

            if (justified) {
                slotStart = slotStart - slotAngle / 2;

                if (slotStart < 0) {
                    slotStart += 360;
                }
            }

            to = limitValue(math.ceil(to || from), from, totalDivs - 1);
            slots = to - from + 1;
            angle = slotAngle * slots;

            return new Ring(
                box.center(), 0, box.height() / 2,
                slotStart, angle
            );
        },

        pointCategoryIndex: function (point) {
            var axis = this,
                index = null,
                i,
                length = axis.options.categories.length,
                slot;

            for (i = 0; i < length; i++) {
                slot = axis.getSlot(i);
                if (slot.containsPoint(point)) {
                    index = i;
                    break;
                }
            }

            return index;
        }
    });
    deepExtend(RadarCategoryAxis.fn, GridLinesMixin);

    var RadarNumericAxisMixin = {
        options: {
            majorGridLines: {
                visible: true
            }
        },

        createPlotBands: function () {
            var axis = this,
                options = axis.options,
                plotBands = options.plotBands || [],
                type = options.majorGridLines.type,
                altAxis = axis.plotArea.polarAxis,
                majorAngles = altAxis.majorAngles(),
                center = altAxis.box.center(),
                i,
                band,
                bandStyle,
                slot,
                ring;

            var group = new draw.Group({
                zIndex: -1
            });

            for (i = 0; i < plotBands.length; i++) {
                band = plotBands[i];
                bandStyle = {
                    fill: {
                        color: band.color,
                        opacity: band.opacity
                    },
                    stroke: {
                        opacity: band.opacity
                    }
                };

                slot = axis.getSlot(band.from, band.to, true);
                ring = new Ring(center, center.y - slot.y2, center.y - slot.y1, 0, 360);

                var shape;
                if (type === ARC) {
                    shape = ShapeBuilder.current.createRing(ring, bandStyle);
                } else {
                    shape = draw.Path.fromPoints(
                            axis.plotBandPoints(ring, majorAngles), bandStyle
                    ).close();
                }

                group.append(shape);
            }

            axis.appendVisual(group);
        },

        plotBandPoints: function (ring, angles) {
            var innerPoints = [],
                outerPoints = [];

            var center = [ring.c.x, ring.c.y];
            var innerCircle = new geom.Circle(center, ring.ir);
            var outerCircle = new geom.Circle(center, ring.r);

            for (var i = 0; i < angles.length; i++) {
                innerPoints.push(innerCircle.pointAt(angles[i]));
                outerPoints.push(outerCircle.pointAt(angles[i]));
            }

            innerPoints.reverse();
            innerPoints.push(innerPoints[0]);
            outerPoints.push(outerPoints[0]);

            return outerPoints.concat(innerPoints);
        },

        createGridLines: function (altAxis) {
            var axis = this,
                options = axis.options,
                majorTicks = axis.radarMajorGridLinePositions(),
                majorAngles = altAxis.majorAngles(),
                minorTicks,
                center = altAxis.box.center(),
                gridLines = [];

            if (options.majorGridLines.visible) {
                gridLines = axis.renderGridLines(
                    center, majorTicks, majorAngles, options.majorGridLines
                );
            }

            if (options.minorGridLines.visible) {
                minorTicks = axis.radarMinorGridLinePositions();
                append(gridLines, axis.renderGridLines(
                    center, minorTicks, majorAngles, options.minorGridLines
                ));
            }

            return gridLines;
        },

        renderGridLines: function (center, ticks, angles, options) {
            var axis = this,
                tickRadius,
                tickIx,
                angleIx;

            var style = {
                stroke: {
                    width: options.width,
                    color: options.color,
                    dashType: options.dashType
                }
            };

            var container = this.gridLinesVisual();
            for (tickIx = 0; tickIx < ticks.length; tickIx++) {
                tickRadius = center.y - ticks[tickIx];
                if (tickRadius > 0) {
                    var circle = new geom.Circle([center.x, center.y], tickRadius);
                    if (options.type === ARC) {
                        container.append(new draw.Circle(circle, style));
                    } else {
                        var line = new draw.Path(style);
                        for (angleIx = 0; angleIx < angles.length; angleIx++) {
                            line.lineTo(circle.pointAt(angles[angleIx]));
                        }

                        line.close();
                        container.append(line);
                    }
                }
            }

            return container.children;
        },

        getValue: function (point) {
            var axis = this,
                options = axis.options,
                lineBox = axis.lineBox(),
                altAxis = axis.plotArea.polarAxis,
                majorAngles = altAxis.majorAngles(),
                center = altAxis.box.center(),
                r = point.distanceTo(center),
                distance = r;

            if (options.majorGridLines.type !== ARC && majorAngles.length > 1) {
                var dx = point.x - center.x,
                    dy = point.y - center.y,
                    theta = (math.atan2(dy, dx) / DEG_TO_RAD + 540) % 360;

                majorAngles.sort(function (a, b) {
                    return angularDistance(a, theta) - angularDistance(b, theta);
                });

                // Solve triangle (center, point, axis X) using one side (r) and two angles.
                // Angles are derived from triangle (center, point, gridline X)
                var midAngle = angularDistance(majorAngles[0], majorAngles[1]) / 2,
                    alpha = angularDistance(theta, majorAngles[0]),
                    gamma = 90 - midAngle,
                    beta = 180 - alpha - gamma;

                distance = r * (math.sin(beta * DEG_TO_RAD) / math.sin(gamma * DEG_TO_RAD));
            }

            return axis.axisType().fn.getValue.call(
                axis, new Point2D(lineBox.x1, lineBox.y2 - distance)
            );
        }
    };

    var RadarNumericAxis = NumericAxis.extend({
        radarMajorGridLinePositions: function () {
            return this.getTickPositions(this.options.majorUnit);
        },

        radarMinorGridLinePositions: function () {
            var axis = this,
                options = axis.options,
                minorSkipStep = 0;
            if (options.majorGridLines.visible) {
                minorSkipStep = options.majorUnit;
            }
            return axis.getTickPositions(options.minorUnit, minorSkipStep);
        },

        axisType: function () {
            return NumericAxis;
        }
    });

    deepExtend(RadarNumericAxis.fn, RadarNumericAxisMixin);

    var RadarLogarithmicAxis = LogarithmicAxis.extend({
        radarMajorGridLinePositions: function () {
            var axis = this,
                positions = [];

            axis.traverseMajorTicksPositions(function (position) {
                positions.push(position);
            }, axis.options.majorGridLines);

            return positions;
        },

        radarMinorGridLinePositions: function () {
            var axis = this,
                positions = [];

            axis.traverseMinorTicksPositions(function (position) {
                positions.push(position);
            }, axis.options.minorGridLines);

            return positions;
        },

        axisType: function () {
            return LogarithmicAxis;
        }
    });

    deepExtend(RadarLogarithmicAxis.fn, RadarNumericAxisMixin);

    var PolarAxis = Axis.extend({
        init: function (options) {
            var axis = this;

            Axis.fn.init.call(axis, options);
            options = axis.options;

            options.minorUnit = options.minorUnit || axis.options.majorUnit / 2;
        },

        options: {
            type: "polar",
            startAngle: 0,
            reverse: false,
            majorUnit: 60,
            min: 0,
            max: 360,
            labels: {
                margin: getSpacing(10)
            },
            majorGridLines: {
                color: BLACK,
                visible: true,
                width: 1
            },
            minorGridLines: {
                color: "#aaa"
            }
        },

        getDivisions: function (stepValue) {
            return NumericAxis.fn.getDivisions.call(this, stepValue) - 1;
        },

        reflow: function (box) {
            this.box = box;
            this.reflowLabels();
        },

        reflowLabels: function () {
            var axis = this,
                measureBox = new Box2D(),
                divs = axis.majorIntervals(),
                labels = axis.labels,
                labelBox,
                i;

            for (i = 0; i < labels.length; i++) {
                labels[i].reflow(measureBox);
                labelBox = labels[i].box;

                labels[i].reflow(axis.getSlot(divs[i]).adjacentBox(
                    0, labelBox.width(), labelBox.height()
                ));
            }
        },

        lineBox: function () {
            return this.box;
        },

        intervals: function (step, skipStep) {
            var axis = this,
                options = axis.options,
                divisions = axis.getDivisions(step),
                angle = options.min,
                divs = [],
                i;

            if (skipStep) {
                skipStep = skipStep / step;
            }

            for (i = 0; i < divisions; i++) {
                if (i % skipStep !== 0) {
                    divs.push((360 + angle) % 360);
                }

                angle += step;
            }

            return divs;
        },

        majorIntervals: function () {
            return this.intervals(this.options.majorUnit);
        },

        minorIntervals: function () {
            return this.intervals(this.options.minorUnit);
        },

        intervalAngle: function (i) {
            return (360 + i - this.options.startAngle) % 360;
        },

        majorAngles: RadarCategoryAxis.fn.majorAngles,

        createLine: function () {
            return [];
        },

        majorGridLineAngles: function (altAxis) {
            return this.gridLineAngles(altAxis, this.options.majorUnit);
        },

        minorGridLineAngles: function (altAxis, skipMajor) {
            return this.gridLineAngles(altAxis, this.options.minorUnit,
                      skipMajor ? this.options.majorUnit : 0);
        },

        createPlotBands: RadarCategoryAxis.fn.createPlotBands,

        plotBandSlot: function (band) {
            return this.getSlot(band.from, band.to);
        },

        getSlot: function (a, b) {
            var axis = this,
                options = axis.options,
                start = options.startAngle,
                box = axis.box,
                tmp;

            a = limitValue(a, options.min, options.max);
            b = limitValue(b || a, a, options.max);

            if (options.reverse) {
                a *= -1;
                b *= -1;
            }

            a = (540 - a - start) % 360;
            b = (540 - b - start) % 360;

            if (b < a) {
                tmp = a;
                a = b;
                b = tmp;
            }

            return new Ring(
                box.center(), 0, box.height() / 2,
                a, b - a
            );
        },

        getValue: function (point) {
            var axis = this,
                options = axis.options,
                center = axis.box.center(),
                dx = point.x - center.x,
                dy = point.y - center.y,
                theta = math.round(math.atan2(dy, dx) / DEG_TO_RAD),
                start = options.startAngle;

            if (!options.reverse) {
                theta *= -1;
                start *= -1;
            }

            return (theta + start + 360) % 360;
        },

        labelsCount: NumericAxis.fn.labelsCount,
        createAxisLabel: NumericAxis.fn.createAxisLabel
    });
    deepExtend(PolarAxis.fn, GridLinesMixin);

    var RadarClusterLayout = ChartElement.extend({
        options: {
            gap: 1,
            spacing: 0
        },

        reflow: function (sector) {
            var cluster = this,
                options = cluster.options,
                children = cluster.children,
                gap = options.gap,
                spacing = options.spacing,
                count = children.length,
                slots = count + gap + (spacing * (count - 1)),
                slotAngle = sector.angle / slots,
                slotSector,
                angle = sector.startAngle + slotAngle * (gap / 2),
                i;

            for (i = 0; i < count; i++) {
                slotSector = sector.clone();
                slotSector.startAngle = angle;
                slotSector.angle = slotAngle;

                if (children[i].sector) {
                    slotSector.r = children[i].sector.r;
                }

                children[i].reflow(slotSector);
                children[i].sector = slotSector;

                angle += slotAngle + (slotAngle * spacing);
            }
        }
    });

    var RadarStackLayout = ChartElement.extend({
        reflow: function (sector) {
            var stack = this,
                reverse = stack.options.isReversed,
                children = stack.children,
                childrenCount = children.length,
                childSector,
                i,
                first = reverse ? childrenCount - 1 : 0,
                step = reverse ? -1 : 1;

            stack.box = new Box2D();

            for (i = first; i >= 0 && i < childrenCount; i += step) {
                childSector = children[i].sector;
                childSector.startAngle = sector.startAngle;
                childSector.angle = sector.angle;
            }
        }
    });

    var RadarSegment = DonutSegment.extend({
        init: function (value, options) {
            DonutSegment.fn.init.call(this, value, null, options);
        },

        options: {
            overlay: {
                gradient: null
            },
            labels: {
                distance: 10
            }
        }
    });

    var RadarBarChart = BarChart.extend({
        pointType: function () {
            return RadarSegment;
        },

        clusterType: function () {
            return RadarClusterLayout;
        },

        stackType: function () {
            return RadarStackLayout;
        },

        categorySlot: function (categoryAxis, categoryIx) {
            return categoryAxis.getSlot(categoryIx);
        },

        pointSlot: function (categorySlot, valueSlot) {
            var slot = categorySlot.clone(),
                y = categorySlot.c.y;

            slot.r = y - valueSlot.y1;
            slot.ir = y - valueSlot.y2;

            return slot;
        },

        reflow: CategoricalChart.fn.reflow,

        reflowPoint: function (point, pointSlot) {
            point.sector = pointSlot;
            point.reflow();
        },

        options: {
            clip: false
        }
    });

    var RadarLineChart = LineChart.extend({
        options: {
            clip: false
        },

        pointSlot: function (categorySlot, valueSlot) {
            var valueRadius = categorySlot.c.y - valueSlot.y1,
                slot = Point2D.onCircle(categorySlot.c, categorySlot.middle(), valueRadius);

            return new Box2D(slot.x, slot.y, slot.x, slot.y);
        },

        createSegment: function (linePoints, currentSeries, seriesIx) {
            var segment,
                pointType,
                style = currentSeries.style;

            if (style == SMOOTH) {
                pointType = SplineSegment;
            } else {
                pointType = LineSegment;
            }

            segment = new pointType(linePoints, currentSeries, seriesIx);

            if (linePoints.length === currentSeries.data.length) {
                segment.options.closed = true;
            }

            return segment;
        }
    });

    var RadarAreaSegment = AreaSegment.extend({
        points: function () {
            return LineSegment.fn.points.call(this, this.stackPoints);
        }
    });

    var SplineRadarAreaSegment = SplineAreaSegment.extend({
        areaPoints: function () {
            return [];
        }
    });

    var RadarAreaChart = RadarLineChart.extend({
        createSegment: function (linePoints, currentSeries, seriesIx, prevSegment) {
            var chart = this,
                options = chart.options,
                isStacked = options.isStacked,
                stackPoints,
                segment,
                style = (currentSeries.line || {}).style;

            if (style === SMOOTH) {
                segment = new SplineRadarAreaSegment(linePoints, prevSegment, isStacked, currentSeries, seriesIx);
                segment.options.closed = true;
            }
            else {
                if (isStacked && seriesIx > 0 && prevSegment) {
                    stackPoints = prevSegment.linePoints.slice(0).reverse();
                }

                linePoints.push(linePoints[0]);
                segment = new RadarAreaSegment(linePoints, stackPoints, currentSeries, seriesIx);
            }

            return segment;
        },

        seriesMissingValues: function (series) {
            return series.missingValues || ZERO;
        }
    });

    var PolarScatterChart = ScatterChart.extend({
        pointSlot: function (slotX, slotY) {
            var valueRadius = slotX.c.y - slotY.y1,
                slot = Point2D.onCircle(slotX.c, slotX.startAngle, valueRadius);

            return new Box2D(slot.x, slot.y, slot.x, slot.y);
        },
        options: {
            clip: false
        }
    });

    var PolarLineChart = ScatterLineChart.extend({
        pointSlot: PolarScatterChart.fn.pointSlot,
        options: {
            clip: false
        }
    });

    var PolarAreaSegment = AreaSegment.extend({
        points: function () {
            var segment = this,
                chart = segment.parent,
                plotArea = chart.plotArea,
                polarAxis = plotArea.polarAxis,
                center = polarAxis.box.center(),
                stackPoints = segment.stackPoints,
                points = LineSegment.fn.points.call(segment, stackPoints);

            points.unshift([center.x, center.y]);
            points.push([center.x, center.y]);

            return points;
        }
    });

    var SplinePolarAreaSegment = SplineAreaSegment.extend({
        areaPoints: function () {
            var segment = this,
                chart = segment.parent,
                plotArea = chart.plotArea,
                polarAxis = plotArea.polarAxis,
                center = polarAxis.box.center();
            return [center];
        },
        points: function () {
            var segment = this,
                chart = segment.parent,
                plotArea = chart.plotArea,
                polarAxis = plotArea.polarAxis,
                center = polarAxis.box.center(),
                curvePoints,
                curveProcessor = new CurveProcessor(false),
                linePoints = LineSegment.fn.points.call(this);
            linePoints.push(center);

            curvePoints = curveProcessor.process(linePoints);
            curvePoints.splice(curvePoints.length - 3, curvePoints.length - 1);
            segment.curvePoints = curvePoints;
            return curvePoints;
        }
    });

    var PolarAreaChart = PolarLineChart.extend({
        createSegment: function (linePoints, currentSeries, seriesIx) {
            var segment,
                style = (currentSeries.line || {}).style;
            if (style == SMOOTH) {
                segment = new SplinePolarAreaSegment(linePoints, null, false, currentSeries, seriesIx);
            }
            else {
                segment = new PolarAreaSegment(linePoints, [], currentSeries, seriesIx);
            }
            return segment;
        },

        seriesMissingValues: function (series) {
            return series.missingValues || ZERO;
        },

        sortPoints: function (points) {
            return points.sort(xComparer);
        }
    });

    var PolarPlotAreaBase = PlotAreaBase.extend({
        init: function (series, options) {
            var plotArea = this;

            plotArea.valueAxisRangeTracker = new AxisGroupRangeTracker();

            PlotAreaBase.fn.init.call(plotArea, series, options);
        },

        render: function () {
            var plotArea = this;

            plotArea.addToLegend(plotArea.series);
            plotArea.createPolarAxis();
            plotArea.createCharts();
            plotArea.createValueAxis();
        },

        createValueAxis: function () {
            var plotArea = this,
                tracker = plotArea.valueAxisRangeTracker,
                defaultRange = tracker.query(),
                range,
                valueAxis,
                axisOptions = plotArea.valueAxisOptions({
                    roundToMajorUnit: false, zIndex: -1
                }),
                axisType,
                axisDefaultRange;

            if (axisOptions.type === LOGARITHMIC) {
                axisType = RadarLogarithmicAxis;
                axisDefaultRange = { min: 0.1, max: 1 };
            } else {
                axisType = RadarNumericAxis;
                axisDefaultRange = { min: 0, max: 1 };
            }

            range = tracker.query(name) || defaultRange || axisDefaultRange;

            if (range && defaultRange) {
                range.min = math.min(range.min, defaultRange.min);
                range.max = math.max(range.max, defaultRange.max);
            }

            valueAxis = new axisType(
                range.min, range.max,
                axisOptions
            );

            plotArea.valueAxis = valueAxis;
            plotArea.appendAxis(valueAxis);
        },

        reflowAxes: function () {
            var plotArea = this,
                options = plotArea.options.plotArea,
                valueAxis = plotArea.valueAxis,
                polarAxis = plotArea.polarAxis,
                box = plotArea.box,
                defaultPadding = math.min(box.width(), box.height()) * DEFAULT_PADDING,
                padding = getSpacing(options.padding || {}, defaultPadding),
                axisBox = box.clone().unpad(padding),
                valueAxisBox = axisBox.clone().shrink(0, axisBox.height() / 2);

            polarAxis.reflow(axisBox);
            valueAxis.reflow(valueAxisBox);
            var heightDiff = valueAxis.lineBox().height() - valueAxis.box.height();
            valueAxis.reflow(valueAxis.box.unpad({ top: heightDiff }));

            plotArea.axisBox = axisBox;
            plotArea.alignAxes(axisBox);
        },

        alignAxes: function () {
            var plotArea = this,
                valueAxis = plotArea.valueAxis,
                slot = valueAxis.getSlot(valueAxis.options.min),
                slotEdge = valueAxis.options.reverse ? 2 : 1,
                center = plotArea.polarAxis.getSlot(0).c,
                box = valueAxis.box.translate(
                    center.x - slot[X + slotEdge],
                    center.y - slot[Y + slotEdge]
                );

            valueAxis.reflow(box);
        },

        backgroundBox: function () {
            return this.box;
        }
    });

    var RadarPlotArea = PolarPlotAreaBase.extend({
        options: {
            categoryAxis: {
                categories: []
            },
            valueAxis: {}
        },

        createPolarAxis: function () {
            var plotArea = this,
                categoryAxis;

            categoryAxis = new RadarCategoryAxis(plotArea.options.categoryAxis);

            plotArea.polarAxis = categoryAxis;
            plotArea.categoryAxis = categoryAxis;
            plotArea.appendAxis(categoryAxis);
        },

        valueAxisOptions: function (defaults) {
            var plotArea = this;

            if (plotArea._hasBarCharts) {
                deepExtend(defaults, {
                    majorGridLines: { type: ARC },
                    minorGridLines: { type: ARC }
                });
            }

            if (plotArea._isStacked100) {
                deepExtend(defaults, {
                    roundToMajorUnit: false,
                    labels: { format: "P0" }
                });
            }

            return deepExtend(defaults, plotArea.options.valueAxis);
        },

        appendChart: CategoricalPlotArea.fn.appendChart,

        createCharts: function () {
            var plotArea = this,
                series = plotArea.filterVisibleSeries(plotArea.series),
                pane = plotArea.panes[0];

            plotArea.createAreaChart(
                filterSeriesByType(series, [RADAR_AREA]),
                pane
            );

            plotArea.createLineChart(
                filterSeriesByType(series, [RADAR_LINE]),
                pane
            );

            plotArea.createBarChart(
                filterSeriesByType(series, [RADAR_COLUMN]),
                pane
            );
        },

        chartOptions: function (series) {
            var options = { series: series };
            var firstSeries = series[0];
            if (firstSeries) {
                var filteredSeries = this.filterVisibleSeries(series);
                var stack = firstSeries.stack;
                options.isStacked = stack && filteredSeries.length > 1;
                options.isStacked100 = stack && stack.type === "100%" && filteredSeries.length > 1;

                if (options.isStacked100) {
                    this._isStacked100 = true;
                }
            }

            return options;
        },

        createAreaChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var areaChart = new RadarAreaChart(this, this.chartOptions(series));
            this.appendChart(areaChart, pane);
        },

        createLineChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var lineChart = new RadarLineChart(this, this.chartOptions(series));
            this.appendChart(lineChart, pane);
        },

        createBarChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var firstSeries = series[0];
            var options = this.chartOptions(series);
            options.gap = firstSeries.gap;
            options.spacing = firstSeries.spacing;

            var barChart = new RadarBarChart(this, options);
            this.appendChart(barChart, pane);

            this._hasBarCharts = true;
        },

        seriesCategoryAxis: function () {
            return this.categoryAxis;
        },

        click: function (chart, e) {
            var plotArea = this,
                coords = chart._eventCoordinates(e),
                point = new Point2D(coords.x, coords.y),
                category,
                value;

            category = plotArea.categoryAxis.getCategory(point);
            value = plotArea.valueAxis.getValue(point);

            if (category !== null && value !== null) {
                chart.trigger(PLOT_AREA_CLICK, {
                    element: $(e.target),
                    category: category,
                    value: value
                });
            }
        }
    });

    var PolarPlotArea = PolarPlotAreaBase.extend({
        options: {
            xAxis: {},
            yAxis: {}
        },

        createPolarAxis: function () {
            var plotArea = this,
                polarAxis;

            polarAxis = new PolarAxis(plotArea.options.xAxis);

            plotArea.polarAxis = polarAxis;
            plotArea.axisX = polarAxis;
            plotArea.appendAxis(polarAxis);
        },

        valueAxisOptions: function (defaults) {
            var plotArea = this;

            return deepExtend(defaults, {
                majorGridLines: { type: ARC },
                minorGridLines: { type: ARC }
            }, plotArea.options.yAxis
            );
        },

        createValueAxis: function () {
            var plotArea = this;

            PolarPlotAreaBase.fn.createValueAxis.call(plotArea);
            plotArea.axisY = plotArea.valueAxis;
        },

        appendChart: function (chart, pane) {
            var plotArea = this;

            plotArea.valueAxisRangeTracker.update(chart.yAxisRanges);

            PlotAreaBase.fn.appendChart.call(plotArea, chart, pane);
        },

        createCharts: function () {
            var plotArea = this,
                series = plotArea.filterVisibleSeries(plotArea.series),
                pane = plotArea.panes[0];

            plotArea.createLineChart(
                filterSeriesByType(series, [POLAR_LINE]),
                pane
            );

            plotArea.createScatterChart(
                filterSeriesByType(series, [POLAR_SCATTER]),
                pane
            );

            plotArea.createAreaChart(
                filterSeriesByType(series, [POLAR_AREA]),
                pane
            );
        },

        createLineChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                lineChart = new PolarLineChart(plotArea, { series: series });

            plotArea.appendChart(lineChart, pane);
        },

        createScatterChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                scatterChart = new PolarScatterChart(plotArea, { series: series });

            plotArea.appendChart(scatterChart, pane);
        },

        createAreaChart: function (series, pane) {
            if (series.length === 0) {
                return;
            }

            var plotArea = this,
                areaChart = new PolarAreaChart(plotArea, { series: series });

            plotArea.appendChart(areaChart, pane);
        },

        click: function (chart, e) {
            var plotArea = this,
                coords = chart._eventCoordinates(e),
                point = new Point2D(coords.x, coords.y),
                xValue,
                yValue;

            xValue = plotArea.axisX.getValue(point);
            yValue = plotArea.axisY.getValue(point);

            if (xValue !== null && yValue !== null) {
                chart.trigger(PLOT_AREA_CLICK, {
                    element: $(e.target),
                    x: xValue,
                    y: yValue
                });
            }
        }
    });

    // Helpers ================================================================
    function xComparer(a, b) {
        return a.value.x - b.value.x;
    }

    function angularDistance(a, b) {
        return 180 - math.abs(math.abs(a - b) - 180);
    }

    // Exports ================================================================
    PlotAreaFactory.current.register(PolarPlotArea, POLAR_CHARTS);
    PlotAreaFactory.current.register(RadarPlotArea, RADAR_CHARTS);

    SeriesBinder.current.register(POLAR_CHARTS, [X, Y], ["color"]);
    SeriesBinder.current.register(RADAR_CHARTS, ["value"], ["color"]);

    dataviz.DefaultAggregates.current.register(
        RADAR_CHARTS,
        { value: "max", color: "first" }
    );

    deepExtend(dataviz, {
        PolarAreaChart: PolarAreaChart,
        PolarAxis: PolarAxis,
        PolarLineChart: PolarLineChart,
        PolarPlotArea: PolarPlotArea,
        RadarAreaChart: RadarAreaChart,
        RadarBarChart: RadarBarChart,
        RadarCategoryAxis: RadarCategoryAxis,
        RadarClusterLayout: RadarClusterLayout,
        RadarLineChart: RadarLineChart,
        RadarNumericAxis: RadarNumericAxis,
        RadarPlotArea: RadarPlotArea,
        SplinePolarAreaSegment: SplinePolarAreaSegment,
        SplineRadarAreaSegment: SplineRadarAreaSegment,
        RadarStackLayout: RadarStackLayout
    });

})(window.kendo.jQuery);





(function ($, undefined) {

    // Imports ================================================================
    var kendo = window.kendo,
        deepExtend = kendo.deepExtend,
        extend = $.extend,
        isFn = kendo.isFunction,
        template = kendo.template,

        util = kendo.util,
        append = util.append,

        draw = kendo.drawing,
        geom = kendo.geometry,
        dataviz = kendo.dataviz,
        Color = kendo.drawing.Color,
        ChartElement = dataviz.ChartElement,
        PieChartMixin = dataviz.PieChartMixin,
        PlotAreaBase = dataviz.PlotAreaBase,
        PlotAreaFactory = dataviz.PlotAreaFactory,
        Point2D = dataviz.Point2D,
        Box2D = dataviz.Box2D,
        SeriesBinder = dataviz.SeriesBinder,
        TextBox = dataviz.TextBox,
        autoFormat = dataviz.autoFormat,
        evalOptions = dataviz.evalOptions,
        limitValue = util.limitValue,
        seriesTotal = dataviz.seriesTotal;

    // Constants ==============================================================
    var CATEGORY = "category",
        COLOR = "color",
        FUNNEL = "funnel",
        VALUE = "value",
        BLACK = "black",
        WHITE = "white";

    // Funnel chart ===========================================================
    var FunnelPlotArea = PlotAreaBase.extend({
        render: function () {
            var plotArea = this,
                series = plotArea.series;

            plotArea.createFunnelChart(series);
        },

        createFunnelChart: function (series) {
            var plotArea = this,
                firstSeries = series[0],
                funnelChart = new FunnelChart(plotArea, {
                    series: series,
                    legend: plotArea.options.legend,
                    neckRatio: firstSeries.neckRatio,
                    dynamicHeight: firstSeries.dynamicHeight,
                    dynamicSlope: firstSeries.dynamicSlope,
                    segmentSpacing: firstSeries.segmentSpacing,
                    highlight: firstSeries.highlight
                });

            plotArea.appendChart(funnelChart);
        },

        appendChart: function (chart, pane) {
            PlotAreaBase.fn.appendChart.call(this, chart, pane);
            append(this.options.legend.items, chart.legendItems);
        }
    });

    var FunnelChart = ChartElement.extend({
        init: function (plotArea, options) {
            var chart = this;

            ChartElement.fn.init.call(chart, options);

            chart.plotArea = plotArea;
            chart.points = [];
            chart.labels = [];
            chart.legendItems = [];
            chart.render();
        },

        options: {
            neckRatio: 0.3,
            width: 300,
            dynamicSlope: false,
            dynamicHeight: true,
            segmentSpacing: 0,
            labels: {
                visible: false,
                align: "center",
                position: "center"
            }
        },

        formatPointValue: function (point, format) {
            return autoFormat(format, point.value);
        },

        render: function () {
            var chart = this,
                options = chart.options,
                colors = chart.plotArea.options.seriesColors || [],
                colorsCount = colors.length,
                series = options.series[0],
                pointData, fields,
                data = series.data;

            if (!data) {
                return;
            }

            var total = seriesTotal(series),
                value,
                i;

            for (i = 0; i < data.length; i++) {
                pointData = SeriesBinder.current.bindPoint(series, i);
                value = pointData.valueFields.value;

                if (value === null || value === undefined) {
                    continue;
                }

                fields = pointData.fields;

                if (!isFn(series.color)) {
                    series.color = fields.color || colors[i % colorsCount];
                }

                fields = deepExtend({
                    index: i,
                    owner: chart,
                    series: series,
                    category: fields.category,
                    dataItem: data[i],
                    percentage: Math.abs(value) / total,
                    visibleInLegend: fields.visibleInLegend,
                    visible: fields.visible
                }, fields);

                var segment = chart.createSegment(value, fields);
                var label = chart.createLabel(value, fields);

                if (segment && label) {
                    segment.append(label);
                }
            }
        },

        evalSegmentOptions: function (options, value, fields) {
            var series = fields.series;

            evalOptions(options, {
                value: value,
                series: series,
                dataItem: fields.dataItem,
                index: fields.index
            }, { defaults: series._defaults, excluded: ["data"] });
        },

        createSegment: function (value, fields) {
            var chart = this,
                segment;

            chart.createLegendItem(value, fields);

            if (fields.visible !== false) {
                var seriesOptions = deepExtend({}, fields.series);
                chart.evalSegmentOptions(seriesOptions, value, fields);

                segment = new FunnelSegment(value, seriesOptions, fields);
                extend(segment, fields);

                chart.append(segment);
                chart.points.push(segment);

                return segment;
            }
        },

        createLabel: function (value, fields) {
            var chart = this,
                series = fields.series,
                dataItem = fields.dataItem,
                labels = deepExtend({}, chart.options.labels, series.labels),
                text = value,
                textBox;

            if (labels.visible) {
                if (labels.template) {
                    var labelTemplate = template(labels.template);
                    text = labelTemplate({
                        dataItem: dataItem,
                        value: value,
                        percentage: fields.percentage,
                        category: fields.category,
                        series: series
                    });
                } else if (labels.format) {
                    text = autoFormat(labels.format, text);
                }

                if (!labels.color && labels.align === "center") {
                    var brightnessValue = new Color(series.color).percBrightness();
                    if (brightnessValue > 180) {
                        labels.color = BLACK;
                    } else {
                        labels.color = WHITE;
                    }
                }

                chart.evalSegmentOptions(labels, value, fields);

                textBox = new TextBox(text, deepExtend({
                    vAlign: labels.position
                }, labels)
                );

                chart.labels.push(textBox);

                return textBox;
            }
        },

        labelPadding: function () {
            var labels = this.labels,
                label,
                align,
                width,
                padding = { left: 0, right: 0 },
                i;

            for (i = 0; i < labels.length; i++) {
                label = labels[i];
                align = label.options.align;
                if (align !== "center") {
                    width = labels[i].box.width();

                    if (align === "left") {
                        padding.left = Math.max(padding.left, width);
                    } else {
                        padding.right = Math.max(padding.right, width);
                    }
                }
            }

            return padding;
        },

        reflow: function (chartBox) {
            var chart = this,
                options = chart.options,
                segments = chart.points,
                count = segments.length,
                decreasingWidth = options.neckRatio <= 1,
                i,
                height,
                lastUpperSide,
                points,
                percentage,
                offset,
                box = chartBox.clone().unpad(chart.labelPadding()),
                width = box.width(),
                previousHeight = 0,
                previousOffset = decreasingWidth ? 0 : (width - width / options.neckRatio) / 2,
                segmentSpacing = options.segmentSpacing,
                dynamicSlope = options.dynamicSlope,
                totalHeight = box.height() - segmentSpacing * (count - 1),
                neckRatio = decreasingWidth ? options.neckRatio * width : width;

            if (!count) {
                return;
            }

            if (dynamicSlope) {
                var firstSegment = segments[0],
                    maxSegment = firstSegment;

                $.each(segments, function (idx, val) {
                    if (val.percentage > maxSegment.percentage) {
                        maxSegment = val;
                    }
                });

                lastUpperSide = (firstSegment.percentage / maxSegment.percentage) * width;
                previousOffset = (width - lastUpperSide) / 2;

                for (i = 0; i < count; i++) {
                    percentage = segments[i].percentage;

                    var nextSegment = segments[i + 1],
                        nextPercentage = (nextSegment ? nextSegment.percentage : percentage);

                    points = segments[i].points = [];
                    height = (options.dynamicHeight) ? (totalHeight * percentage) : (totalHeight / count);
                    offset = (width - lastUpperSide * (nextPercentage / percentage)) / 2;
                    offset = limitValue(offset, 0, width);

                    points.push(new geom.Point(box.x1 + previousOffset, box.y1 + previousHeight));
                    points.push(new geom.Point(box.x1 + width - previousOffset, box.y1 + previousHeight));
                    points.push(new geom.Point(box.x1 + width - offset, box.y1 + height + previousHeight));
                    points.push(new geom.Point(box.x1 + offset, box.y1 + height + previousHeight));

                    previousOffset = offset;
                    previousHeight += height + segmentSpacing;
                    lastUpperSide *= nextPercentage / percentage;
                    lastUpperSide = limitValue(lastUpperSide, 0, width);
                }
            }
            else {
                var topMostWidth = decreasingWidth ? width : width - previousOffset * 2,
                    finalNarrow = (topMostWidth - neckRatio) / 2;

                for (i = 0; i < count; i++) {
                    points = segments[i].points = [];
                    percentage = segments[i].percentage;
                    offset = (options.dynamicHeight) ? (finalNarrow * percentage) : (finalNarrow / count);
                    height = (options.dynamicHeight) ? (totalHeight * percentage) : (totalHeight / count);

                    points.push(new geom.Point(box.x1 + previousOffset, box.y1 + previousHeight));
                    points.push(new geom.Point(box.x1 + width - previousOffset, box.y1 + previousHeight));
                    points.push(new geom.Point(box.x1 + width - previousOffset - offset, box.y1 + height + previousHeight));
                    points.push(new geom.Point(box.x1 + previousOffset + offset, box.y1 + height + previousHeight));
                    previousOffset += offset;
                    previousHeight += height + segmentSpacing;
                }
            }

            for (i = 0; i < count; i++) {
                segments[i].reflow(chartBox);
            }
        }
    });

    deepExtend(FunnelChart.fn, PieChartMixin);

    var FunnelSegment = ChartElement.extend({
        init: function (value, options, segmentOptions) {
            var segment = this;

            ChartElement.fn.init.call(segment, options);

            segment.value = value;
            segment.options.index = segmentOptions.index;
        },

        options: {
            color: WHITE,
            border: {
                width: 1
            }
        },

        reflow: function (chartBox) {
            var segment = this,
                points = segment.points,
                label = segment.children[0];

            segment.box = new Box2D(points[0].x, points[0].y, points[1].x, points[2].y);

            if (label) {
                label.reflow(new Box2D(chartBox.x1, points[0].y, chartBox.x2, points[2].y));
            }
        },

        createVisual: function () {
            ChartElement.fn.createVisual.call(this);

            var options = this.options;
            var border = options.border;
            var path = draw.Path.fromPoints(this.points, {
                fill: {
                    color: options.color,
                    opacity: options.opacity
                },
                stroke: {
                    color: border.color,
                    opacity: border.opacity,
                    width: border.width
                }
            }).close();

            this.visual.append(path);
        },

        createHighlight: function (style) {
            return draw.Path.fromPoints(this.points, style);
        },

        highlightOverlay: function (view, opt) {
            var options = this.options,
                hlOptions = options.highlight || {};
            if (hlOptions.visible === false) {
                return;
            }
            var border = hlOptions.border || {};
            var calcOptions = extend({}, opt, {
                fill: hlOptions.color,
                stroke: border.color,
                strokeOpacity: border.opacity,
                strokeWidth: border.width,
                fillOpacity: hlOptions.opacity
            });
            var element = view.createPolyline(this.points, true, calcOptions);
            return element;
        },

        tooltipAnchor: function (tooltipWidth) {
            var box = this.box;
            return new Point2D(
                box.center().x - (tooltipWidth / 2),
                box.y1
            );
        },
        formatValue: function (format) {
            var point = this;
            return point.owner.formatPointValue(point, format);
        }
    });
    deepExtend(FunnelSegment.fn, dataviz.PointEventsMixin);

    // Exports ================================================================
    PlotAreaFactory.current.register(FunnelPlotArea, [FUNNEL]);

    SeriesBinder.current.register(
        [FUNNEL],
        [VALUE], [CATEGORY, COLOR, "visibleInLegend", "visible"]
    );

    deepExtend(dataviz, {
        FunnelChart: FunnelChart
    });

})(window.kendo.jQuery);





(function ($, undefined) {

    var math = Math,
        kendo = window.kendo,
        util = kendo.util,
        Widget = kendo.ui.Widget,
        deepExtend = kendo.deepExtend,

        dataviz = kendo.dataviz,
        autoMajorUnit = dataviz.autoMajorUnit,
        ChartElement = dataviz.ChartElement,
        NumericAxis = dataviz.NumericAxis,
        Axis = dataviz.Axis,
        Box2D = dataviz.Box2D,
        Class = kendo.Class,
        defined = util.defined,
        isArray = $.isArray,
        isNumber = util.isNumber,
        interpolateValue = dataviz.interpolateValue,
        valueOrDefault = util.valueOrDefault,

        getSpacing = dataviz.getSpacing,
        round = dataviz.round,
        geo = dataviz.geometry,
        draw = dataviz.drawing,
        Point = geo.Point,
        Circle = draw.Circle,
        Group = draw.Group,
        Path = draw.Path,
        Rect = geo.Rect,
        Text = draw.Text,
        Surface = draw.Surface;

    // Constants ==============================================================
    var ANGULAR_SPEED = 150,
        LINEAR_SPEED = 250,
        ARROW = "arrow",
        ARROW_POINTER = "arrowPointer",
        BAR_POINTER = "barPointer",
        BLACK = "#000",
        CAP_SIZE = 0.05,
        COORD_PRECISION = dataviz.COORD_PRECISION,
        MAX_VALUE = Number.MAX_VALUE,
        MIN_VALUE = -Number.MAX_VALUE,
        DEFAULT_HEIGHT = 200,
        DEFAULT_LINE_WIDTH = 0.5,
        DEFAULT_WIDTH = 200,
        DEFAULT_MIN_WIDTH = 60,
        DEFAULT_MIN_HEIGHT = 60,
        DEFAULT_MARGIN = 5,
        DEGREE = math.PI / 180,
        GEO_ARC_ADJUST_ANGLE = 180,
        INSIDE = "inside",
        LINEAR = "linear",
        NEEDLE = "needle",
        OUTSIDE = "outside",
        RADIAL_POINTER = "radialPointer",
        X = "x",
        Y = "y";

    var Pointer = Class.extend({
        init: function (scale, options) {
            var pointer = this;
            var scaleOptions = scale.options;

            ChartElement.fn.init.call(pointer, options);

            options = pointer.options;

            options.fill = options.color;

            pointer.scale = scale;

            if (defined(options.value)) {
                options.value = math.min(math.max(options.value, scaleOptions.min), scaleOptions.max);
            } else {
                options.value = scaleOptions.min;
            }
        },

        options: {
            color: BLACK
        },

        value: function (newValue) {
            var that = this;
            var options = that.options;
            var value = options.value;
            var scaleOptions = that.scale.options;

            if (arguments.length === 0) {
                return value;
            }

            options._oldValue = (options._oldValue !== undefined) ? options.value : scaleOptions.min;
            options.value = math.min(math.max(newValue, scaleOptions.min), scaleOptions.max);

            if (that.elements) {
                that.repaint();
            }
        }
    });

    var RadialPointer = Pointer.extend({
        options: {
            shape: NEEDLE,
            cap: {
                size: CAP_SIZE
            },
            arrow: {
                width: 16,
                height: 14
            },
            animation: {
                type: RADIAL_POINTER,
                duration: ANGULAR_SPEED
            }
        },

        setRadius: function (radius) {
            var that = this;

            if (radius) {
                that.elements.clear();
                that.render(that.parent, that.center, radius);
            }
        },

        setAngle: function (angle) {
            this.elements.transform(geo.transform().rotate(angle, this.center));
        },

        repaint: function () {
            var that = this;
            var scale = that.scale;
            var options = that.options;
            var oldAngle = scale.slotAngle(options._oldValue);
            var newAngle = scale.slotAngle(options.value);

            if (options.animation.transitions === false) {
                that.setAngle(newAngle);
            } else {
                new RadialPointerAnimation(that.elements, deepExtend(options.animation, {
                    oldAngle: oldAngle,
                    newAngle: newAngle
                })).play();
            }
        },

        render: function () {
            var that = this;
            var scale = that.scale;
            var center = scale.arc.center;
            var options = that.options;
            var minAngle = scale.slotAngle(scale.options.min);
            var elements = new Group();

            if (options.animation !== false) {
                deepExtend(options.animation, {
                    startAngle: 0,
                    center: center,
                    reverse: scale.options.reverse
                });
            }

            if (options.shape === NEEDLE) {
                elements.append(
                    that._renderNeedle(),
                    that._renderCap()
                );
            } else {
                elements.append(that._renderArrow());
            }

            that.elements = elements;
            that.setAngle(DEGREE);

            return elements;
        },

        reflow: function (arc) {
            var that = this;
            var center = that.center = arc.center;
            var radius = that.radius = arc.getRadiusX();
            var capSize = that.capSize = Math.round(radius * that.options.cap.size);

            that.bbox = Rect.fromPoints(new Point(center.x - capSize, center.y - capSize),
                                        new Point(center.x + capSize, center.y + capSize));
        },

        _renderNeedle: function () {
            var that = this;
            var options = that.options;
            var minorTickSize = that.scale.options.minorTicks.size;
            var center = that.center;
            var needleColor = options.color;

            var needlePath = new Path({
                fill: { color: needleColor },
                stroke: { color: needleColor, width: DEFAULT_LINE_WIDTH }
            });

            needlePath.moveTo(center.x + that.radius - minorTickSize, center.y)
                      .lineTo(center.x, center.y - (that.capSize / 2))
                      .lineTo(center.x, center.y + (that.capSize / 2))
                      .close();

            return needlePath;
        },

        _renderCap: function () {
            var that = this;
            var options = that.options;
            var capColor = options.cap.color || options.color;
            var circle = new geo.Circle(that.center, that.capSize);

            var cap = new draw.Circle(circle, {
                fill: { color: capColor },
                stroke: { color: capColor }
            });

            return cap;
        }
    });

    var RadialScale = NumericAxis.extend({
        init: function (options) {
            var scale = this;

            scale.options = deepExtend({}, scale.options, options);
            scale.options.majorUnit = scale.options.majorUnit || autoMajorUnit(scale.options.min, scale.options.max);

            scale.options.minorUnit = scale.options.minorUnit || scale.options.majorUnit / 10;

            Axis.fn.init.call(scale, scale.options);
        },

        options: {
            min: 0,
            max: 100,

            majorTicks: {
                size: 15,
                align: INSIDE,
                color: BLACK,
                width: DEFAULT_LINE_WIDTH,
                visible: true
            },

            minorTicks: {
                size: 10,
                align: INSIDE,
                color: BLACK,
                width: DEFAULT_LINE_WIDTH,
                visible: true
            },

            startAngle: -30,
            endAngle: 210,

            labels: {
                position: INSIDE,
                padding: 2
            }
        },

        render: function (center, radius) {
            var that = this;
            var options = that.options;
            var arc = that.renderArc(center, radius);

            that.bbox = arc.bbox();
            that.labelElements = that.renderLabels();
            that.ticks = that.renderTicks();
            that.ranges = that.renderRanges();
        },

        reflow: function (bbox) {
            var that = this;
            var options = that.options;
            var center = bbox.center();
            var radius = math.min(bbox.height(), bbox.width()) / 2;

            if (that.bbox !== undefined) {
                that.bbox = that.arc.bbox();
                that.radius(that.arc.getRadiusX());
                that.repositionRanges();
                that.renderLabels();
            } else {
                return that.render(center, radius);
            }
        },

        slotAngle: function (value) {
            var options = this.options;
            var startAngle = options.startAngle;
            var reverse = options.reverse;
            var angle = options.endAngle - startAngle;
            var min = options.min;
            var max = options.max;
            var result;

            if (reverse) {
                result = options.endAngle - (value - min) / (max - min) * angle;
            } else {
                result = ((value - min) / (max - min) * angle) + startAngle;
            }

            return result + GEO_ARC_ADJUST_ANGLE;
        },

        renderLabels: function () {
            var that = this;
            var options = that.options;
            var majorTickSize = options.majorTicks.size;
            var arc = that.arc.clone();
            var radius = arc.getRadiusX();
            var tickAngles = that.tickAngles(arc, options.majorUnit);
            var labels = that.labels;
            var count = labels.length;
            var labelsOptions = options.labels;
            var padding = labelsOptions.padding;
            var rangeDistance = radius * 0.05;
            var rangeSize = options.rangeSize = options.rangeSize || radius * 0.1;
            var ranges = options.ranges || [];
            var halfWidth, halfHeight, labelAngle;
            var angle, label, lp, i, cx, cy, isInside;
            var labelsGroup = new Group();
            var lbl, labelPos, prevLabelPos, labelTransform;

            if (that.options.rangeDistance !== undefined) {
                rangeDistance = that.options.rangeDistance;
            } else {
                that.options.rangeDistance = rangeDistance;
            }

            if (labelsOptions.position === INSIDE) {
                radius -= majorTickSize;

                if (ranges.length && that.labelElements === undefined) {
                    radius -= rangeSize + rangeDistance;
                }
                arc.setRadiusX(radius).setRadiusY(radius);
            }

            for (i = 0; i < count; i++) {
                label = labels[i];
                halfWidth = label.box.width() / 2;
                halfHeight = label.box.height() / 2;
                angle = tickAngles[i];
                labelAngle = (angle - GEO_ARC_ADJUST_ANGLE) * DEGREE;
                isInside = labelsOptions.position === INSIDE;
                lp = arc.pointAt(angle);
                cx = lp.x + (math.cos(labelAngle) * (halfWidth + padding) * (isInside ? 1 : -1));
                cy = lp.y + (math.sin(labelAngle) * (halfHeight + padding) * (isInside ? 1 : -1));

                label.reflow(new dataviz.Box2D(cx - halfWidth, cy - halfHeight,
                                               cx + halfWidth, cy + halfHeight));
                labelPos = new Point(label.box.x1, label.box.y1);

                if (that.labelElements === undefined) {
                    lbl = _buildLabel(label, options.labels);
                    labelsGroup.append(lbl);
                } else {
                    lbl = that.labelElements.children[i];
                    prevLabelPos = lbl.bbox().origin;

                    labelTransform = lbl.transform() || geo.transform();
                    labelTransform.translate(labelPos.x - prevLabelPos.x, labelPos.y - prevLabelPos.y);
                    lbl.transform(labelTransform);
                }

                that.bbox = Rect.union(that.bbox, lbl.bbox());
            }

            return labelsGroup;
        },

        repositionRanges: function () {
            var that = this;
            var arc = that.arc;
            var ranges = that.ranges.children;
            var rangeSize = that.options.rangeSize;
            var rangeDistance = that.options.rangeDistance;
            var rangeRadius, newRadius;

            if (ranges.length > 0) {
                rangeRadius = that.getRangeRadius();

                if (that.options.labels.position === INSIDE) {
                    rangeRadius += rangeSize + rangeDistance;
                }

                newRadius = rangeRadius + (rangeSize / 2);

                for (var i = 0; i < ranges.length; i++) {
                    ranges[i]._geometry.setRadiusX(newRadius).setRadiusY(newRadius);
                }

                that.bbox = Rect.union(that.bbox, that.ranges.bbox());
            }
        },

        renderRanges: function () {
            var that = this;
            var arc = that.arc;
            var result = new Group();
            var from, to;
            var segments = that.rangeSegments();
            var segmentsCount = segments.length;
            var reverse = that.options.reverse;
            var radius = that.radius();
            var rangeSize = that.options.rangeSize;
            var rangeDistance = that.options.rangeDistance;
            var segment, rangeRadius, rangeGeom, i;

            if (segmentsCount) {
                rangeRadius = that.getRangeRadius();

                // move the ticks with a range distance and a range size
                that.radius(that.radius() - rangeSize - rangeDistance);

                for (i = 0; i < segmentsCount; i++) {
                    segment = segments[i];
                    from = that.slotAngle(segment[reverse ? "to" : "from"]);
                    to = that.slotAngle(segment[!reverse ? "to" : "from"]);

                    if (to - from !== 0) {
                        rangeGeom = new geo.Arc(arc.center, {
                            radiusX: rangeRadius + (rangeSize / 2),
                            radiusY: rangeRadius + (rangeSize / 2),
                            startAngle: from,
                            endAngle: to
                        });

                        result.append(new draw.Arc(rangeGeom, {
                            stroke: {
                                width: rangeSize,
                                color: segment.color,
                                opacity: segment.opacity
                            }
                        })
                        );
                    }
                }
            }

            return result;
        },

        rangeSegments: function () {
            var gauge = this;
            var options = gauge.options;
            var ranges = options.ranges || [];
            var count = ranges.length;
            var range;
            var segmentsCount;
            var defaultColor = options.rangePlaceholderColor;
            var segments = [];
            var segment;
            var min = options.min;
            var max = options.max;
            var i, j;

            function rangeSegment(from, to, color, opacity) {
                return { from: from, to: to, color: color, opacity: opacity };
            }

            if (count) {
                segments.push(rangeSegment(min, max, defaultColor));

                for (i = 0; i < count; i++) {
                    range = getRange(ranges[i], min, max);
                    segmentsCount = segments.length;
                    for (j = 0; j < segmentsCount; j++) {
                        segment = segments[j];
                        if (segment.from <= range.from && range.from <= segment.to) {
                            segments.push(rangeSegment(range.from, range.to, range.color, range.opacity));
                            if (segment.from <= range.to && range.to <= segment.to) {
                                segments.push(rangeSegment(range.to, segment.to, defaultColor, range.opacity));
                            }
                            segment.to = range.from;
                            break;
                        }
                    }
                }
            }

            return segments;
        },

        getRangeRadius: function () {
            var that = this;
            var options = that.options;
            var majorTickSize = options.majorTicks.size;
            var rangeSize = options.rangeSize;
            var rangeDistance = options.rangeDistance;
            var arc = that.arc;
            var r;

            if (options.labels.position === OUTSIDE) {
                r = arc.getRadiusX() - majorTickSize - rangeDistance - rangeSize;
            } else {
                r = arc.getRadiusX() - rangeSize;
            }

            return r;
        },

        renderArc: function (center, radius) {
            var that = this;
            var options = that.options;

            var arc = that.arc = new geo.Arc(center, {
                radiusX: radius,
                radiusY: radius,
                startAngle: options.startAngle + GEO_ARC_ADJUST_ANGLE,
                endAngle: options.endAngle + GEO_ARC_ADJUST_ANGLE
            });

            return arc;
        },

        renderTicks: function () {
            var that = this;
            var arc = that.arc;
            var options = that.options;
            var labelsPosition = options.labels.position;
            var allTicks = new Group();
            var majorTickSize = options.majorTicks.size;
            var minorTickSize = options.minorTicks.size;
            var tickArc = arc.clone();
            var radius = tickArc.getRadiusX();

            function drawTicks(arc, tickAngles, unit, tickOptions) {
                var ticks = new Group(),
                    center = arc.center,
                    radius = arc.getRadiusX(),
                    i, tickStart, tickEnd,
                    tickSize = unit.size,
                    visible = tickOptions.visible;

                if (visible) {
                    for (i = 0; i < tickAngles.length; i++) {
                        tickStart = arc.pointAt(tickAngles[i]);
                        tickEnd = new Point(center.x + radius - tickOptions.size, center.y).rotate(tickAngles[i], center);

                        ticks.append(new Path({
                            stroke: {
                                color: tickOptions.color,
                                width: tickOptions.width
                            }
                        }).moveTo(tickStart).lineTo(tickEnd));
                    }
                }

                return ticks;
            }

            that.majorTickAngles = that.tickAngles(arc, options.majorUnit);
            that.majorTicks = drawTicks(tickArc, that.majorTickAngles, options.majorUnit, options.majorTicks);
            allTicks.append(that.majorTicks);

            that._tickDifference = majorTickSize - minorTickSize;
            if (labelsPosition === OUTSIDE) {
                tickArc.setRadiusX(radius - majorTickSize + minorTickSize)
                       .setRadiusY(radius - majorTickSize + minorTickSize);
            }

            that.minorTickAngles = that.normalizeTickAngles(that.tickAngles(arc, options.minorUnit));
            that.minorTicks = drawTicks(tickArc, that.minorTickAngles, options.minorUnit, options.minorTicks, options.majorUnit);
            allTicks.append(that.minorTicks);

            return allTicks;
        },

        normalizeTickAngles: function (angles) {
            var that = this;
            var options = that.options;
            var skip = options.majorUnit / options.minorUnit;

            for (var i = angles.length - 1; i >= 0; i--) {
                if (i % skip === 0) {
                    angles.splice(i, 1);
                }
            }

            return angles;
        },

        tickAngles: function (ring, stepValue) {
            var scale = this;
            var options = scale.options;
            var reverse = options.reverse;
            var range = options.max - options.min;
            var angle = ring.endAngle - ring.startAngle;
            var pos = ring.startAngle;
            var tickCount = range / stepValue;
            var step = angle / tickCount;
            var positions = [];
            var i;

            if (reverse) {
                pos += angle;
                step = -step;
            }

            for (i = 0; i < tickCount; i++) {
                positions.push(round(pos, COORD_PRECISION));
                pos += step;
            }

            if (round(pos) <= ring.endAngle) {
                positions.push(pos);
            }

            return positions;
        },

        radius: function (radius) {
            var that = this;
            var parent = that.parent;
            var center = that.arc.center;

            if (radius) {
                that.arc.setRadiusX(radius).setRadiusY(radius);
                that.repositionTicks(that.majorTicks.children, that.majorTickAngles);
                that.repositionTicks(that.minorTicks.children, that.minorTickAngles, true);
            } else {
                return that.arc.getRadiusX();
            }
        },

        repositionTicks: function (ticks, tickAngles, minor) {
            var that = this;
            var diff = minor ? (that._tickDifference || 0) : 0;
            var tickArc = that.arc;
            var radius = tickArc.getRadiusX();

            if (minor && that.options.labels.position === OUTSIDE && diff !== 0) {
                tickArc = that.arc.clone();
                tickArc.setRadiusX(radius - diff).setRadiusY(radius - diff);
            }

            for (var i = 0; i < ticks.length; i++) {
                var newPoint = tickArc.pointAt(tickAngles[i]);
                var segments = ticks[i].segments;
                var xDiff = newPoint.x - segments[0].anchor().x;
                var yDiff = newPoint.y - segments[0].anchor().y;

                ticks[i].transform(new geo.Transformation().translate(xDiff, yDiff));
            }
        }
    });

    var Gauge = Widget.extend({
        init: function (element, userOptions) {
            var gauge = this;
            var options;
            var themeOptions;
            var themeName;
            var themes = dataviz.ui.themes || {};
            var theme;

            kendo.destroy(element);
            $(element).empty();

            Widget.fn.init.call(gauge, element);

            gauge.wrapper = gauge.element;

            gauge._originalOptions = deepExtend({}, userOptions);
            options = deepExtend({}, gauge.options, userOptions);

            themeName = options.theme;
            theme = themes[themeName] || themes[themeName.toLowerCase()];
            themeOptions = themeName && theme ? theme.gauge : {};

            gauge.options = deepExtend({}, themeOptions, options);

            if ($.isArray(options.pointer)) {
                for (var i = 0; i < options.pointer.length; i++) {
                    gauge.options.pointer[i] = deepExtend({}, themeOptions.pointer, options.pointer[i]);
                }
            }

            gauge.element.addClass("k-gauge");

            gauge.surface = gauge._createSurface();

            gauge.redraw();
        },

        options: {
            plotArea: {},
            theme: "default",
            renderAs: "",
            pointer: {},
            scale: {},
            gaugeArea: {}
        },

        destroy: function () {
            this.surface.destroy();

            Widget.fn.destroy.call(this);
        },

        value: function (value) {

            var that = this;
            var pointer = that.pointers[0];

            if (arguments.length === 0) {
                return pointer.value();
            }

            pointer.value(value);
        },

        _draw: function () {
            var surface = this.surface;

            surface.clear();
            surface.draw(this._visuals);
        },

        exportVisual: function () {
            return this._visuals;
        },

        allValues: function (values) {
            var that = this;
            var pointers = that.pointers;
            var allValues = [];
            var i;

            if (arguments.length === 0) {
                for (i = 0; i < pointers.length; i++) {
                    allValues.push(pointers[i].value());
                }

                return allValues;
            }

            if ($.isArray(values)) {
                for (i = 0; i < values.length; i++) {
                    if (isNumber(values[i])) {
                        pointers[i].value(values[i]);
                    }
                }
            }
        },

        _resize: function () {
            var that = this;
            var t = that.options.transitions;
            var i;

            that.options.transitions = false;
            for (i = 0; i < that.pointers.length; i++) {
                that.pointers[i].options.animation.transitions = false;
            }

            that.redraw();

            that.options.transitions = t;
            for (i = 0; i < that.pointers.length; i++) {
                that.pointers[i].options.animation.transitions = t;
            }
        },

        redraw: function () {
            var that = this;
            var size = deepExtend(that._getSize(), that.options.gaugeArea);
            var wrapper = new Rect([0, 0], [size.width, size.height]);
            var bbox;

            that.surface.clear();
            that.gaugeArea = that._createGaugeArea();
            that._createModel();

            bbox = _unpad(wrapper.bbox(), that._gaugeAreaMargin);
            that.reflow(bbox);
        },

        _createGaugeArea: function () {
            var that = this;
            var options = that.options.gaugeArea;
            var size = that.surface.size();
            var margin = that._gaugeAreaMargin = options.margin || DEFAULT_MARGIN;
            var border = options.border || {};
            var areaGeometry = new Rect([0, 0], [size.width, size.height]);

            if (border.width > 0) {
                areaGeometry = _unpad(areaGeometry, border.width);
            }

            var gaugeArea = Path.fromRect(areaGeometry, {
                stroke: {
                    color: border.width ? border.color : "",
                    width: border.width,
                    dashType: border.dashType,
                    lineJoin: "round",
                    lineCap: "round"
                },
                fill: {
                    color: options.background
                }
            });

            return gaugeArea;
        },

        _createSurface: function () {
            var that = this;
            var options = that.options;
            var size = that._getSize();
            size = options.gaugeArea ? deepExtend(size, options.gaugeArea) : size;

            var wrap = $("<div></div>").appendTo(that.element).css({
                width: size.width,
                height: size.height
            });

            return new draw.Surface.create(wrap, {
                type: options.renderAs
            });
        },

        getSize: function () {
            return this._getSize();
        },

        _getSize: function () {
            var that = this;
            var element = that.element;
            var width = element.width();
            var height = element.height();

            if (!width) {
                width = DEFAULT_WIDTH;
            }

            if (!height) {
                height = DEFAULT_HEIGHT;
            }

            return { width: width, height: height };
        }
    });

    var RadialGauge = Gauge.extend({
        init: function (element, options) {
            var radialGauge = this;

            Gauge.fn.init.call(radialGauge, element, options);

            kendo.notify(radialGauge, dataviz.ui);
        },

        options: {
            name: "RadialGauge",
            transitions: true,
            gaugeArea: {
                background: ""
            }
        },

        reflow: function (bbox) {
            var that = this;
            var pointers = that.pointers;
            var scaleElements = that.scale.reflow(bbox);
            that._initialPlotArea = that.scale.bbox;

            for (var i = 0; i < pointers.length; i++) {
                var pointerElement = pointers[i].reflow(that.scale.arc);
                that._initialPlotArea = Rect.union(that._initialPlotArea, pointers[i].bbox);
            }

            that.fitScale(bbox);
            that.alignScale(bbox);
            that._buildVisual(that.gaugeArea, pointers, that.scale);
            that._draw();
        },

        _buildVisual: function (gaugeArea, pointers, scale) {
            var visuals = new Group();
            var current;

            visuals.append(gaugeArea);
            visuals.append(scale.ticks);
            visuals.append(scale.ranges);

            for (var i = 0; i < pointers.length; i++) {
                current = pointers[i];
                current.render();
                visuals.append(current.elements);
                current.value(current.options.value);
            }
            visuals.append(scale.labelElements);

            this._visuals = visuals;
        },

        fitScale: function (bbox) {
            var that = this;
            var scale = that.scale;
            var arc = scale.arc;
            var plotAreaBox = that._initialPlotArea;
            var step = math.abs(that.getDiff(plotAreaBox, bbox));
            var min = round(step, COORD_PRECISION);
            var max = round(-step, COORD_PRECISION);
            var minDiff, midDiff, maxDiff, mid;
            var i = 0;

            while (i < 100) {
                i++;
                if (min != mid) {
                    minDiff = that.getPlotBox(min, bbox, arc);
                    if (0 <= minDiff && minDiff <= 2) {
                        break;
                    }
                }

                if (max != mid) {
                    maxDiff = that.getPlotBox(max, bbox, arc);
                    if (0 <= maxDiff && maxDiff <= 2) {
                        break;
                    }
                }

                if (minDiff > 0 && maxDiff > 0) {
                    mid = min * 2;
                } else if (minDiff < 0 && maxDiff < 0) {
                    mid = max * 2;
                } else {
                    mid = round(((min + max) / 2) || 1, COORD_PRECISION);
                }

                midDiff = that.getPlotBox(mid, bbox, arc);
                if (0 <= midDiff && midDiff <= 2) {
                    break;
                }

                if (midDiff > 0) {
                    max = mid;
                    maxDiff = midDiff;
                } else {
                    min = mid;
                    minDiff = midDiff;
                }
            }
        },

        getPlotBox: function (step, bbox, arc) {
            var that = this;
            var scale = that.scale;
            var pointers = that.pointers;
            var radius = arc.getRadiusX();

            arc = arc.clone();
            arc.setRadiusX(radius + step).setRadiusY(radius + step);

            scale.arc = arc;
            scale.reflow(bbox);
            that.plotBbox = scale.bbox;

            for (var i = 0; i < pointers.length; i++) {
                pointers[i].reflow(arc);
                that.plotBbox = Rect.union(that.plotBbox, pointers[i].bbox);
            }

            return that.getDiff(that.plotBbox, bbox);
        },

        getDiff: function (plotBox, box) {
            return math.min(box.width() - plotBox.width(), box.height() - plotBox.height());
        },

        alignScale: function (bbox) {
            var that = this;
            var plotBoxCenter = that.plotBbox.center();
            var boxCenter = bbox.center();
            var paddingX = plotBoxCenter.x - boxCenter.x;
            var paddingY = plotBoxCenter.y - boxCenter.y;
            var scale = that.scale;
            var pointers = that.pointers;

            scale.arc.center.x -= paddingX;
            scale.arc.center.y -= paddingY;

            scale.reflow(bbox);

            for (var i = 0; i < pointers.length; i++) {
                pointers[i].reflow(scale.arc);
                that.plotBbox = Rect.union(scale.bbox, pointers[i].bbox);
            }
        },

        _createModel: function () {
            var that = this;
            var options = that.options;
            var pointers = options.pointer;
            var scale = that.scale = new RadialScale(options.scale);
            var current;

            that.pointers = [];

            pointers = $.isArray(pointers) ? pointers : [pointers];
            for (var i = 0; i < pointers.length; i++) {
                current = new RadialPointer(scale,
                    deepExtend({}, pointers[i], {
                        animation: {
                            transitions: options.transitions
                        }
                    }));
                that.pointers.push(current);
            }
        }
    });

    var LinearGauge = Gauge.extend({
        init: function (element, options) {
            var linearGauge = this;

            Gauge.fn.init.call(linearGauge, element, options);

            kendo.notify(linearGauge, dataviz.ui);
        },

        options: {
            name: "LinearGauge",
            transitions: true,
            gaugeArea: {
                background: ""
            },
            scale: {
                vertical: true
            }
        },

        reflow: function (bbox) {
            var that = this;
            var surface = that.surface;
            var pointers = that.pointers;
            var bboxX = bbox.origin.x;
            var bboxY = bbox.origin.y;

            var bbox2D = new dataviz.Box2D(bboxX, bboxX,
                                           bboxX + bbox.width(), bboxY + bbox.height());

            that.scale.reflow(bbox2D);

            for (var i = 0; i < pointers.length; i++) {
                pointers[i].reflow();
            }

            that.bbox = that._getBox(bbox2D);
            that._alignElements();
            that._shrinkElements();
            that._buildVisual();
            that._draw();
        },

        _buildVisual: function () {
            var that = this;
            var visuals = new Group();
            var scaleElements = that.scale.render();
            var pointers = that.pointers;
            var current;

            visuals.append(that.gaugeArea);
            visuals.append(scaleElements);

            for (var i = 0; i < pointers.length; i++) {
                current = pointers[i];
                visuals.append(current.render());
                current.value(current.options.value);
            }

            that._visuals = visuals;
        },

        _createModel: function () {
            var that = this;
            var options = that.options;
            var pointers = options.pointer;
            var scale = that.scale = new LinearScale(options.scale);
            var current, currentOptions;

            that.pointers = [];

            pointers = $.isArray(pointers) ? pointers : [pointers];
            for (var i = 0; i < pointers.length; i++) {
                currentOptions = deepExtend({}, pointers[i], {
                    animation: {
                        transitions: options.transitions
                    }
                });

                if (currentOptions.shape === ARROW) {
                    current = new ArrowLinearPointer(scale, currentOptions);
                } else {
                    current = new BarLinearPointer(scale, currentOptions);
                }

                that.pointers.push(current);
            }
        },

        _getSize: function () {
            var gauge = this;
            var element = gauge.element;
            var width = element.width();
            var height = element.height();
            var vertical = gauge.options.scale.vertical;

            if (!width) {
                width = vertical ? DEFAULT_MIN_WIDTH : DEFAULT_WIDTH;
            }

            if (!height) {
                height = vertical ? DEFAULT_HEIGHT : DEFAULT_MIN_HEIGHT;
            }

            return { width: width, height: height };
        },

        _getBox: function (box) {
            var that = this;
            var scale = that.scale;
            var pointers = that.pointers;
            var boxCenter = box.center();
            var plotAreaBox = pointers[0].box.clone().wrap(scale.box);
            var size;

            for (var i = 0; i < pointers.length; i++) {
                plotAreaBox.wrap(pointers[i].box.clone());
            }

            if (scale.options.vertical) {
                size = plotAreaBox.width() / 2;
                plotAreaBox = new Box2D(
                    boxCenter.x - size, box.y1,
                    boxCenter.x + size, box.y2
                );
            } else {
                size = plotAreaBox.height() / 2;
                plotAreaBox = new Box2D(
                    box.x1, boxCenter.y - size,
                    box.x2, boxCenter.y + size
                );
            }

            return plotAreaBox;
        },

        _alignElements: function () {
            var that = this;
            var scale = that.scale;
            var pointers = that.pointers;
            var scaleBox = scale.box;
            var box = pointers[0].box.clone().wrap(scale.box);
            var plotAreaBox = that.bbox;
            var diff, i;

            for (i = 0; i < pointers.length; i++) {
                box.wrap(pointers[i].box.clone());
            }

            if (scale.options.vertical) {
                diff = plotAreaBox.center().x - box.center().x;
                scale.reflow(new Box2D(
                    scaleBox.x1 + diff, plotAreaBox.y1,
                    scaleBox.x2 + diff, plotAreaBox.y2
                ));
            } else {
                diff = plotAreaBox.center().y - box.center().y;
                scale.reflow(new Box2D(
                    plotAreaBox.x1, scaleBox.y1 + diff,
                    plotAreaBox.x2, scaleBox.y2 + diff
                ));
            }

            for (i = 0; i < pointers.length; i++) {
                pointers[i].reflow(that.bbox);
            }
        },

        _shrinkElements: function () {
            var that = this;
            var scale = that.scale;
            var pointers = that.pointers;
            var scaleBox = scale.box.clone();
            var pos = scale.options.vertical ? "y" : "x";
            var pointerBox = pointers[0].box;
            var i;

            for (i = 0; i < pointers.length; i++) {
                pointerBox.wrap(pointers[i].box.clone());
            }

            scaleBox[pos + 1] += math.max(scaleBox[pos + 1] - pointerBox[pos + 1], 0);
            scaleBox[pos + 2] -= math.max(pointerBox[pos + 2] - scaleBox[pos + 2], 0);

            scale.reflow(scaleBox);

            for (i = 0; i < pointers.length; i++) {
                pointers[i].reflow(that.bbox);
            }
        }
    });

    var LinearScale = NumericAxis.extend({
        init: function (options) {
            var scale = this;

            scale.options = deepExtend({}, scale.options, options);
            scale.options = deepExtend({}, scale.options, { labels: { mirror: scale.options.mirror} });
            scale.options.majorUnit = scale.options.majorUnit || autoMajorUnit(scale.options.min, scale.options.max);

            Axis.fn.init.call(scale, scale.options);
            scale.options.minorUnit = scale.options.minorUnit || scale.options.majorUnit / 10;
        },

        options: {
            min: 0,
            max: 50,

            majorTicks: {
                size: 15,
                align: INSIDE,
                color: BLACK,
                width: DEFAULT_LINE_WIDTH,
                visible: true
            },

            minorTicks: {
                size: 10,
                align: INSIDE,
                color: BLACK,
                width: DEFAULT_LINE_WIDTH,
                visible: true
            },

            line: {
                width: DEFAULT_LINE_WIDTH
            },

            labels: {
                position: INSIDE,
                padding: 2
            },
            mirror: false,
            _alignLines: false
        },

        render: function () {
            var that = this;
            var elements = that.elements = new Group();
            var labels = that.renderLabels();
            var scaleLine = that.renderLine();
            var scaleTicks = that.renderTicks();
            var ranges = that.renderRanges();

            elements.append(scaleLine, labels, scaleTicks, ranges);

            return elements;
        },

        renderRanges: function () {
            var that = this;
            var options = that.options;
            var min = options.min;
            var max = options.max;
            var ranges = options.ranges || [];
            var vertical = options.vertical;
            var mirror = options.labels.mirror;
            var elements = new Group();
            var count = ranges.length;
            var rangeSize = options.rangeSize || options.minorTicks.size / 2;
            var range, slot, slotX, slotY, i;

            if (count) {
                for (i = 0; i < count; i++) {
                    range = getRange(ranges[i], min, max);
                    slot = that.getSlot(range.from, range.to);
                    slotX = vertical ? that.lineBox() : slot;
                    slotY = vertical ? slot : that.lineBox();
                    if (vertical) {
                        slotX.x1 -= rangeSize * (mirror ? -1 : 1);
                    } else {
                        slotY.y2 += rangeSize * (mirror ? -1 : 1);
                    }

                    elements.append(Path.fromRect(new Rect([slotX.x1, slotY.y1],
                        [slotX.x2 - slotX.x1, slotY.y2 - slotY.y1]), {
                            fill: { color: range.color, opacity: range.opacity },
                            stroke: {}
                        }));
                }
            }

            return elements;
        },

        renderLabels: function () {
            var that = this;
            var options = that.options;
            var labels = that.labels;
            var elements = new Group();

            for (var i = 0; i < labels.length; i++) {
                elements.append(_buildLabel(labels[i], options.labels));
            }

            return elements;
        },

        renderLine: function () {
            var that = this;
            var options = that.options;
            var line = options.line;
            var lineBox = that.lineBox();
            var linePath;
            var elements = new Group();

            if (line.width > 0 && line.visible) {
                linePath = new Path({
                    stroke: {
                        color: line.color,
                        dashType: line.dashType,
                        width: line.width
                    }
                });

                linePath.moveTo(lineBox.x1, lineBox.y1).lineTo(lineBox.x2, lineBox.y2);
                elements.append(linePath);
            }

            return elements;
        },

        renderTicks: function () {
            var that = this;
            var ticks = new Group();
            var options = that.options;
            var lineBox = that.lineBox();
            var mirror = options.labels.mirror;
            var majorUnit = options.majorTicks.visible ? options.majorUnit : 0;
            var tickLineOptions = {
                _alignLines: options._alignLines,
                vertical: options.vertical
            };
            var start, end;

            function render(tickPositions, tickOptions) {
                var i, count = tickPositions.length;

                if (tickOptions.visible) {
                    for (i = tickOptions.skip; i < count; i += tickOptions.step) {
                        if (i % tickOptions.skipUnit === 0) {
                            continue;
                        }

                        tickLineOptions.tickX = mirror ? lineBox.x2 : lineBox.x2 - tickOptions.size;
                        tickLineOptions.tickY = mirror ? lineBox.y1 - tickOptions.size : lineBox.y1;
                        tickLineOptions.position = tickPositions[i];

                        ticks.append(that.renderAxisTick(tickLineOptions, tickOptions));
                    }
                }
            }

            render(that.getMajorTickPositions(), options.majorTicks);
            render(that.getMinorTickPositions(), deepExtend({}, {
                skipUnit: majorUnit / options.minorUnit
            }, options.minorTicks));

            return ticks;
        },

        renderAxisTick: function (options, tickOptions) {
            var tickX = options.tickX;
            var tickY = options.tickY;
            var position = options.position;
            var start, end, tickPath;

            if (options.vertical) {
                start = new Point(tickX, position);
                end = new Point(tickX + tickOptions.size, position);
            } else {
                start = new Point(position, tickY);
                end = new Point(position, tickY + tickOptions.size);
            }

            tickPath = new Path({
                stroke: {
                    color: tickOptions.color,
                    width: tickOptions.width
                }
            }).moveTo(start).lineTo(end);

            return tickPath;
        }
    });

    var LinearPointer = Pointer.extend({
        init: function (scale, options) {
            var pointer = this;

            Pointer.fn.init.call(pointer, scale, options);

            pointer.options = deepExtend({
                track: {
                    visible: defined(options.track)
                }
            }, pointer.options);
        },

        options: {
            shape: BAR_POINTER,

            track: {
                border: {
                    width: 1
                }
            },

            color: BLACK,
            border: {
                width: 1
            },
            opacity: 1,

            margin: getSpacing(3),
            animation: {
                type: BAR_POINTER
            },
            visible: true
        },

        reflow: function () {
            var pointer = this;
            var options = pointer.options;
            var scale = pointer.scale;
            var scaleLine = scale.lineBox();
            var trackSize = options.track.size || options.size;
            var pointerHalfSize = options.size / 2;
            var mirror = scale.options.mirror;
            var margin = getSpacing(options.margin);
            var vertical = scale.options.vertical;
            var space = vertical ?
                     margin[mirror ? "left" : "right"] :
                     margin[mirror ? "bottom" : "top"];
            var pointerBox, pointerRangeBox, trackBox;

            space = mirror ? -space : space;

            if (vertical) {
                trackBox = new Box2D(
                    scaleLine.x1 + space, scaleLine.y1,
                    scaleLine.x1 + space, scaleLine.y2);

                if (mirror) {
                    trackBox.x1 -= trackSize;
                } else {
                    trackBox.x2 += trackSize;
                }

                if (options.shape !== BAR_POINTER) {
                    pointerRangeBox = new Box2D(
                        scaleLine.x2 + space, scaleLine.y1 - pointerHalfSize,
                        scaleLine.x2 + space, scaleLine.y2 + pointerHalfSize
                    );
                    pointerBox = pointerRangeBox;
                }
            } else {
                trackBox = new Box2D(
                    scaleLine.x1, scaleLine.y1 - space,
                    scaleLine.x2, scaleLine.y1 - space);

                if (mirror) {
                    trackBox.y2 += trackSize;
                } else {
                    trackBox.y1 -= trackSize;
                }

                if (options.shape !== BAR_POINTER) {
                    pointerRangeBox = new Box2D(
                        scaleLine.x1 - pointerHalfSize, scaleLine.y1 - space,
                        scaleLine.x2 + pointerHalfSize, scaleLine.y1 - space
                    );
                    pointerBox = pointerRangeBox;
                }
            }

            pointer.trackBox = trackBox;
            pointer.pointerRangeBox = pointerRangeBox;
            pointer.box = pointerBox || trackBox.clone().pad(options.border.width);
        },

        getElementOptions: function () {
            var options = this.options;
            var elements = new Group();
            var scale = this.scale;

            return {
                fill: {
                    color: options.color,
                    opacity: options.opacity
                },
                stroke: defined(options.border) ? {
                    color: options.border.width ? options.border.color || options.color : "",
                    width: options.border.width,
                    dashType: options.border.dashType,
                    opacity: options.opacity
                } : null
            };
        },

        _margin: function () {
            var pointer = this;
            var options = pointer.options;
            var scale = pointer.scale;
            var mirror = scale.options.mirror;
            var margin = getSpacing(options.margin);
            var vertical = scale.options.vertical;

            var space = vertical ?
                     margin[mirror ? "left" : "right"] :
                     margin[mirror ? "bottom" : "top"];

            return space;
        }
    });

    var ArrowLinearPointer = LinearPointer.extend({
        init: function (scale, options) {
            LinearPointer.fn.init.call(this, scale, options);

            if (this.options.size === undefined) {
                this.options.size = this.scale.options.majorTicks.size * 0.6;
            }
        },

        pointerShape: function (value) {
            var that = this;
            var options = that.options;
            var scale = that.scale;
            var size = options.size;
            var vertical = scale.options.vertical;
            var halfSize = size / 2;
            var sign = (scale.options.mirror ? -1 : 1);
            var reverse = scale.options.reverse;
            var pos, shape;

            if (vertical) {
                pos = reverse ? "y2" : "y1";
                shape = [
                    new Point(0, 0 - halfSize), new Point(0 - sign * size, 0), new Point(0, 0 + halfSize)
                ];
            } else {
                pos = reverse ? "x1" : "x2";
                shape = [
                    new Point(0 - halfSize, 0), new Point(0, 0 + sign * size), new Point(0 + halfSize, 0)
                ];
            }

            return shape;
        },

        repaint: function () {
            var that = this;
            var scale = that.scale;
            var options = that.options;
            var animation = new ArrowLinearPointerAnimation(that.elements, deepExtend(options.animation, {
                vertical: scale.options.vertical,
                mirror: scale.options.mirror,
                margin: that._margin(options.margin),
                from: scale.getSlot(options._oldValue),
                to: scale.getSlot(options.value)
            }));

            if (options.animation.transitions === false) {
                animation.options.duration = 0;
            }

            animation.setup();
            animation.play();
        },

        render: function () {
            var that = this;
            var options = that.options;
            var elements = new Group();
            var scale = that.scale;
            var elementOptions = that.getElementOptions();
            var shape = that.pointerShape(options.value);

            options.animation.type = ARROW_POINTER;

            elements = new Path({
                stroke: elementOptions.stroke,
                fill: elementOptions.fill
            }).moveTo(shape[0]).lineTo(shape[1]).lineTo(shape[2]).close();

            var slot = scale.getSlot(options.value);
            elements.transform(geo.transform().translate(slot.x1, slot.y1));

            that.elements = elements;

            return elements;
        }
    });

    var BarLinearPointer = LinearPointer.extend({
        init: function (scale, options) {
            LinearPointer.fn.init.call(this, scale, options);

            if (this.options.size === undefined) {
                this.options.size = this.scale.options.majorTicks.size * 0.3;
            }
        },

        pointerShape: function (value) {
            var that = this;
            var options = that.options;
            var scale = that.scale;
            var vertical = scale.options.vertical;
            var mirror = scale.options.mirror;
            var dir = mirror == vertical ? -1 : 1;
            var size = options.size * dir;
            var minSlot = scale.getSlot(scale.options.min);
            var slot = scale.getSlot(value);
            var axis = vertical ? Y : X;
            var sizeAxis = vertical ? X : Y;
            var margin = that._margin() * dir;

            var shape = [];
            var p1 = new Point();
            p1[axis] = minSlot[axis + "1"];
            p1[sizeAxis] = minSlot[sizeAxis + "1"];

            var p2 = new Point();
            p2[axis] = slot[axis + "1"];
            p2[sizeAxis] = slot[sizeAxis + "1"];

            if (vertical) {
                p1.translate(margin, 0);
                p2.translate(margin, 0);
            } else {
                p1.translate(0, margin);
                p2.translate(0, margin);
            }

            var p3 = p2.clone();
            var p4 = p1.clone();

            if (vertical) {
                p3.translate(size, 0);
                p4.translate(size, 0);
            } else {
                p3.translate(0, size);
                p4.translate(0, size);
            }

            return [p1, p2, p3, p4];
        },

        repaint: function () {
            var that = this;
            var scale = that.scale;
            var options = that.options;
            var shape = that.pointerShape(options.value);
            var pointerPath = that.elements.children[0];
            var oldShape = that.pointerShape(options._oldValue);

            pointerPath.moveTo(shape[0]).lineTo(shape[1]).lineTo(shape[2]).lineTo(shape[3]).close();

            var animation = new BarLinearPointerAnimation(pointerPath, deepExtend(options.animation, {
                reverse: scale.options.reverse,
                vertical: scale.options.vertical,
                oldPoints: [oldShape[1], oldShape[2]],
                newPoints: [shape[1], shape[2]]
            }));

            if (options.animation.transitions === false) {
                animation.options.duration = 0;
            }

            animation.setup();
            animation.play();
        },

        render: function () {
            var that = this;
            var options = that.options;
            var group = new Group();
            var scale = that.scale;
            var elementOptions = that.getElementOptions();

            var pointer = new Path({
                stroke: elementOptions.stroke,
                fill: elementOptions.fill
            });

            group.append(pointer);

            that.elements = group;

            return group;
        }
    });

    var RadialPointerAnimation = draw.Animation.extend({
        init: function (element, options) {
            draw.Animation.fn.init.call(this, element, options);

            options = this.options;

            options.duration = math.max((math.abs(options.newAngle - options.oldAngle) / options.duration) * 1000, 1);
        },

        options: {
            easing: LINEAR,
            duration: ANGULAR_SPEED
        },

        step: function (pos) {
            var anim = this;
            var options = anim.options;
            var angle = interpolateValue(options.oldAngle, options.newAngle, pos);

            anim.element.transform(geo.transform().rotate(angle, options.center));
        }
    });
    draw.AnimationFactory.current.register(RADIAL_POINTER, RadialPointerAnimation);

    var ArrowLinearPointerAnimation = draw.Animation.extend({
        options: {
            easing: LINEAR,
            duration: LINEAR_SPEED
        },

        setup: function () {
            var options = this.options;
            var halfSize = this.element.bbox().width() / 2;
            var margin = options.margin;
            var from = options.from;
            var to = options.to;
            var axis = options.vertical ? "x1" : "y1";

            if (options.mirror == options.vertical) {
                from[axis] -= margin; to[axis] -= margin;
            } else {
                from[axis] += margin; to[axis] += margin;
            }

            var fromScale = this.fromScale = new Point(from.x1, from.y1);
            var toScale = this.toScale = new Point(to.x1, to.y1);

            if (options.duration !== 0) {
                options.duration = math.max((fromScale.distanceTo(toScale) / options.duration) * 1000, 1);
            }
        },

        step: function (pos) {
            var translateX = interpolateValue(this.fromScale.x, this.toScale.x, pos);
            var translateY = interpolateValue(this.fromScale.y, this.toScale.y, pos);

            this.element.transform(geo.transform().translate(translateX, translateY));
        }
    });
    draw.AnimationFactory.current.register(ARROW_POINTER, ArrowLinearPointerAnimation);

    var BarLinearPointerAnimation = draw.Animation.extend({
        options: {
            easing: LINEAR,
            speed: LINEAR_SPEED
        },

        setup: function () {
            var element = this.element;
            var options = this.options;

            var newPoints = options.newPoints;
            var oldPoints = options.oldPoints;
            var axis = this.axis = options.vertical ? Y : X;
            var to = this.to = newPoints[0][axis];
            var from = this.from = oldPoints[0][axis];

            if (options.duration !== 0) {
                options.duration = math.max((math.abs(to - from) / options.speed) * 1000, 1);
            }

            this._set(from);
        },

        step: function (pos) {
            var value = interpolateValue(this.from, this.to, pos);
            this._set(value);
        },

        _set: function (value) {
            var setter = "set" + this.axis.toUpperCase();
            var points = this.options.newPoints;

            points[0][setter](value);
            points[1][setter](value);
        }
    });
    draw.AnimationFactory.current.register(BAR_POINTER, BarLinearPointerAnimation);

    function _buildLabel(label, options) {
        var labelBox = label.box;
        var textBox = label.children[0].box;
        var border = options.border || {};
        var background = options.background || "";
        var elements = new Group();
        var styleBox, styleGeometry, wrapper;

        wrapper = Path.fromRect(new Rect([labelBox.x1, labelBox.y1], [labelBox.width(), labelBox.height()]), {
            stroke: {}
        });

        var text = new Text(label.text, new Point(textBox.x1, textBox.y1), {
            font: options.font,
            fill: { color: options.color }
        });

        styleGeometry = _pad(text.bbox().clone(), options.padding);

        styleBox = Path.fromRect(styleGeometry, {
            stroke: {
                color: border.width ? border.color : "",
                width: border.width,
                dashType: border.dashType,
                lineJoin: "round",
                lineCap: "round"
            },
            fill: {
                color: background
            }
        });

        elements.append(wrapper);
        elements.append(styleBox);
        elements.append(text);

        return elements;
    }

    function getRange(range, min, max) {
        var from = defined(range.from) ? range.from : MIN_VALUE;
        var to = defined(range.to) ? range.to : MAX_VALUE;

        range.from = math.max(math.min(to, from), min);
        range.to = math.min(math.max(to, from), max);

        return range;
    }

    function _pad(bbox, value) {
        var origin = bbox.getOrigin();
        var size = bbox.getSize();
        var spacing = getSpacing(value);

        bbox.setOrigin([origin.x - spacing.left, origin.y - spacing.top]);
        bbox.setSize([size.width + (spacing.left + spacing.right),
                      size.height + (spacing.top + spacing.bottom)]);

        return bbox;
    }

    function _unpad(bbox, value) {
        var spacing = getSpacing(value);

        spacing.left = -spacing.left; spacing.top = -spacing.top;
        spacing.right = -spacing.right; spacing.bottom = -spacing.bottom;

        return _pad(bbox, spacing);
    }

    dataviz.ui.plugin(RadialGauge);
    dataviz.ui.plugin(LinearGauge);
    dataviz.ExportMixin.extend(Gauge.fn);

    deepExtend(dataviz, {
        Gauge: Gauge,
        RadialPointer: RadialPointer,
        LinearPointer: LinearPointer,
        ArrowLinearPointer: ArrowLinearPointer,
        BarLinearPointer: BarLinearPointer,
        LinearScale: LinearScale,
        RadialScale: RadialScale,
        LinearGauge: LinearGauge,
        RadialGauge: RadialGauge
    });

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        Widget = kendo.ui.Widget,

        extend = $.extend,
        deepExtend = kendo.deepExtend,
        inArray = $.inArray,
        isPlainObject = $.isPlainObject,

        draw = kendo.drawing,
        geom = kendo.geometry,
        util = kendo.util,
        defined = util.defined,
        append = util.append,
        dataviz = kendo.dataviz,
        Box2D = dataviz.Box2D,
        TextBox = dataviz.TextBox,
        DEFAULT_WIDTH = 300,
        DEFAULT_HEIGHT = 100,
        DEFAULT_QUIETZONE_LENGTH = 10,
        numberRegex = /^\d+$/,
        alphanumericRegex = /^[a-z0-9]+$/i,
        InvalidCharacterErrorTemplate = "Character '{0}' is not valid for symbology {1}";

    function getNext(value, index, count) {
        return value.substring(index, index + count);
    }

    var Encoding = kendo.Class.extend({
        init: function (options) {
            this.setOptions(options);
        },
        setOptions: function (options) {
            var that = this;
            that.options = extend({}, that.options, options);
            that.quietZoneLength = that.options.addQuietZone ? 2 * that.options.quietZoneLength : 0;
        },
        encode: function (value, width, height) {
            var that = this;
            if (defined(value)) {
                value += '';
            }

            that.initValue(value, width, height);

            if (that.options.addQuietZone) {
                that.addQuietZone();
            }

            that.addData();

            if (that.options.addQuietZone) {
                that.addQuietZone();
            }

            return {
                baseUnit: that.baseUnit,
                pattern: that.pattern
            };
        },
        options: {
            quietZoneLength: DEFAULT_QUIETZONE_LENGTH,
            addQuietZone: true,
            addCheckSum: true
        },
        initValue: function () { },
        addQuietZone: function () {
            this.pattern.push(this.options.quietZoneLength || DEFAULT_QUIETZONE_LENGTH);
        },
        addData: function () {
        },
        invalidCharacterError: function (character) {
            throw new Error(kendo.format(InvalidCharacterErrorTemplate, character, this.name));
        }
    });

    var encodings = {};

    var code39Base = Encoding.extend({
        minBaseUnitLength: 0.7,
        addData: function () {
            var that = this,
                value = that.value;

            that.addStart();

            for (var idx = 0; idx < value.length; idx++) {
                that.addCharacter(value.charAt(idx));
            }

            if (that.options.addCheckSum) {
                that.pushCheckSum();
            }

            that.addStop();
            that.prepareValues();
        },
        addCharacter: function (character) {
            var that = this,
                charData = that.characterMap[character];
            if (!charData) {
                that.invalidCharacterError(character);
            }
            that.addBase(charData);
        },
        addBase: function () { }
    });

    var code39ExtendedBase = {
        addCharacter: function (character) {
            var that = this;
            if (that.characterMap[character]) {
                that.addBase(that.characterMap[character]);
            }
            else if (character.charCodeAt(0) > 127) {
                that.invalidCharacterError(character);
            }
            else {
                that.addExtended(character.charCodeAt(0));
            }
        },
        addExtended: function (code) {
            var that = this,
                patterns;
            for (var i = 0; i < that.extendedMappings.length; i++) {
                if ((patterns = that.extendedMappings[i].call(that, code))) {
                    for (var j = 0; j < patterns.length; j++) {
                        that.addBase(patterns[j]);
                    }
                    that.dataLength += patterns.length - 1;
                    return;
                }
            }
        },
        extendedMappings: [
            function (code) {
                if (97 <= code && code <= 122) {
                    var that = this;
                    return [that.characterMap[that.shiftCharacters[0]], that.characterMap[String.fromCharCode(code - 32)]];
                }
            },
            function (code) {
                if (33 <= code && code <= 58) {
                    var that = this;
                    return [that.characterMap[that.shiftCharacters[1]], that.characterMap[String.fromCharCode(code + 32)]];
                }
            },
            function (code) {
                if (1 <= code && code <= 26) {
                    var that = this;
                    return [that.characterMap[that.shiftCharacters[2]], that.characterMap[String.fromCharCode(code + 64)]];
                }
            },
            function (code) {
                var that = this,
                    result,
                    dataCharacter;
                if (!that.specialAsciiCodes[code]) {
                    dataCharacter = Math.floor(code / 32) * 6 + (code - 27) % 32 + 64;
                    result = [that.characterMap[that.shiftCharacters[3]], that.characterMap[String.fromCharCode(dataCharacter)]];
                }
                else {
                    result = [];
                    for (var i = 0; i < that.specialAsciiCodes[code].length; i++) {
                        result.push(that.characterMap[that.shiftCharacters[3]]);
                        result.push(that.characterMap[that.specialAsciiCodes[code][i]]);
                    }
                }

                return result;
            }
        ],
        specialAsciiCodes: {
            "0": ["U"],
            "64": ["V"],
            "96": ["W"],
            "127": ["T", "X", "Y", "Z"]
        },
        shiftValuesAsciiCodes: {
            "39": 36,
            "40": 47,
            "41": 43,
            "42": 37
        },
        characterMap: {
            "+": false,
            "/": false,
            "$": false,
            "%": false
        },
        shiftCharacters: ["SHIFT0", "SHIFT1", "SHIFT2", "SHIFT3"]
    };

    encodings.code39 = code39Base.extend({
        name: "Code 39",
        checkSumMod: 43,
        minRatio: 2.5,
        maxRatio: 3,
        gapWidth: 1,
        splitCharacter: "|",
        initValue: function (value, width, height) {
            var that = this;
            that.width = width;
            that.height = height;
            that.value = value;
            that.dataLength = value.length;
            that.pattern = [];
            that.patternString = "";
        },
        prepareValues: function () {
            var that = this,
                baseUnit,
                minBaseUnit = that.minBaseUnitLength,
                ratio = that.maxRatio,
                minRatio = that.minRatio,
                minHeight = Math.max(0.15 * that.width, 24);
            if (that.height < minHeight) {
                throw new Error("Insufficient Height. The minimum height for value: " + that.value + " is: " + minHeight);
            }

            while ((baseUnit = that.getBaseUnit(ratio)) < minBaseUnit && ratio > minRatio) {
                ratio = parseFloat((ratio - 0.1).toFixed(1));
            }

            if (baseUnit < minBaseUnit) {
                var minWidth = Math.ceil(that.getBaseWidth(minRatio) * minBaseUnit);
                throw new Error("Insufficient width. The minimum width for value: " + that.value + " is: " + minWidth);
            }

            that.ratio = ratio;
            that.baseUnit = baseUnit;
            that.patternString = that.patternString.substring(0, that.patternString.length - 1);
            that.pattern = that.pattern.concat(that.patternString.replace(/ratio/g, ratio).split(that.splitCharacter));
        },
        getBaseUnit: function (ratio) {
            return this.width / this.getBaseWidth(ratio);
        },
        getBaseWidth: function (ratio) {
            var that = this,
                characterLength = 3 * (ratio + 2);
            return that.quietZoneLength + characterLength * (that.dataLength + 2) + that.gapWidth * (that.dataLength + 1);
        },
        addStart: function () {
            var that = this;
            that.addPattern(that.characterMap.START.pattern);
            that.addCharacterGap();
        },
        addBase: function (character) {
            this.addPattern(character.pattern);
            this.addCharacterGap();
        },
        addStop: function () {
            this.addPattern(this.characterMap.START.pattern);
        },
        addPattern: function (pattern) {
            for (var i = 0; i < pattern.length; i++) {
                this.patternString += this.patternMappings[pattern.charAt(i)];
            }
        },
        addCharacterGap: function () {
            var that = this;
            that.patternString += that.gapWidth + that.splitCharacter;
        },
        patternMappings: {
            "b": "1|",
            "w": "1|",
            "B": "ratio|",
            "W": "ratio|"
        },
        characterMap: {
            "0": { "pattern": "bwbWBwBwb", "value": 0 },
            "1": { "pattern": "BwbWbwbwB", "value": 1 },
            "2": { "pattern": "bwBWbwbwB", "value": 2 },
            "3": { "pattern": "BwBWbwbwb", "value": 3 },
            "4": { "pattern": "bwbWBwbwB", "value": 4 },
            "5": { "pattern": "BwbWBwbwb", "value": 5 },
            "6": { "pattern": "bwBWBwbwb", "value": 6 },
            "7": { "pattern": "bwbWbwBwB", "value": 7 },
            "8": { "pattern": "BwbWbwBwb", "value": 8 },
            "9": { "pattern": "bwBWbwBwb", "value": 9 },
            "A": { "pattern": "BwbwbWbwB", "value": 10 },
            "B": { "pattern": "bwBwbWbwB", "value": 11 },
            "C": { "pattern": "BwBwbWbwb", "value": 12 },
            "D": { "pattern": "bwbwBWbwB", "value": 13 },
            "E": { "pattern": "BwbwBWbwb", "value": 14 },
            "F": { "pattern": "bwBwBWbwb", "value": 15 },
            "G": { "pattern": "bwbwbWBwB", "value": 16 },
            "H": { "pattern": "BwbwbWBwb", "value": 17 },
            "I": { "pattern": "bwBwbWBwb", "value": 18 },
            "J": { "pattern": "bwbwBWBwb", "value": 19 },
            "K": { "pattern": "BwbwbwbWB", "value": 20 },
            "L": { "pattern": "bwBwbwbWB", "value": 21 },
            "M": { "pattern": "BwBwbwbWb", "value": 22 },
            "N": { "pattern": "bwbwBwbWB", "value": 23 },
            "O": { "pattern": "BwbwBwbWb", "value": 24 },
            "P": { "pattern": "bwBwBwbWb", "value": 25 },
            "Q": { "pattern": "bwbwbwBWB", "value": 26 },
            "R": { "pattern": "BwbwbwBWb", "value": 27 },
            "S": { "pattern": "bwBwbwBWb", "value": 28 },
            "T": { "pattern": "bwbwBwBWb", "value": 29 },
            "U": { "pattern": "BWbwbwbwB", "value": 30 },
            "V": { "pattern": "bWBwbwbwB", "value": 31 },
            "W": { "pattern": "BWBwbwbwb", "value": 32 },
            "X": { "pattern": "bWbwBwbwB", "value": 33 },
            "Y": { "pattern": "BWbwBwbwb", "value": 34 },
            "Z": { "pattern": "bWBwBwbwb", "value": 35 },
            "-": { "pattern": "bWbwbwBwB", "value": 36 },
            ".": { "pattern": "BWbwbwBwb", "value": 37 },
            " ": { "pattern": "bWBwbwBwb", "value": 38 },
            "$": { "pattern": "bWbWbWbwb", "value": 39 },
            "/": { "pattern": "bWbWbwbWb", "value": 40 },
            "+": { "pattern": "bWbwbWbWb", "value": 41 },
            "%": { "pattern": "bwbWbWbWb", "value": 42 },
            START: { pattern: "bWbwBwBwb" }
        },
        options: {
            addCheckSum: false
        }
    });

    encodings.code39extended = encodings.code39.extend(deepExtend({}, code39ExtendedBase, {
        name: "Code 39 extended",
        characterMap: {
            SHIFT0: { "pattern": "bWbwbWbWb", "value": 41 },
            SHIFT1: { "pattern": "bWbWbwbWb", "value": 40 },
            SHIFT2: { "pattern": "bWbWbWbwb", "value": 39 },
            SHIFT3: { "pattern": "bwbWbWbWb", "value": 42 }
        }
    }));

    encodings.code93 = code39Base.extend({
        name: "Code 93",
        cCheckSumTotal: 20,
        kCheckSumTotal: 15,
        checkSumMod: 47,
        initValue: function (value, width, height) {
            var that = this;
            that.value = value;
            that.width = width;
            that.height = height;
            that.pattern = [];
            that.values = [];
            that.dataLength = value.length;
        },
        prepareValues: function () {
            var that = this,
                minHeight = Math.max(0.15 * that.width, 24);
            if (that.height < minHeight) {
                throw new Error("Insufficient Height");
            }

            that.setBaseUnit();

            if (that.baseUnit < that.minBaseUnitLength) {
                throw new Error("Insufficient Width");
            }
        },
        setBaseUnit: function () {
            var that = this,
                checkSumLength = 2;
            that.baseUnit = that.width / (9 * (that.dataLength + 2 + checkSumLength) + that.quietZoneLength + 1);
        },
        addStart: function () {
            var pattern = this.characterMap.START.pattern;
            this.addPattern(pattern);
        },
        addStop: function () {
            var that = this;
            that.addStart();
            that.pattern.push(that.characterMap.TERMINATION_BAR);
        },
        addBase: function (charData) {
            this.addPattern(charData.pattern);
            this.values.push(charData.value);
        },
        pushCheckSum: function () {
            var that = this,
                checkValues = that._getCheckValues(),
                charData;

            that.checksum = checkValues.join("");
            for (var i = 0; i < checkValues.length; i++) {
                charData = that.characterMap[that._findCharacterByValue(checkValues[i])];
                that.addPattern(charData.pattern);
            }
        },
        _getCheckValues: function () {
            var that = this,
                values = that.values,
                length = values.length,
                wightedSum = 0,
                cValue,
                kValue,
                idx;

            for (idx = length - 1; idx >= 0; idx--) {
                wightedSum += that.weightedValue(values[idx], length - idx, that.cCheckSumTotal);
            }
            cValue = wightedSum % that.checkSumMod;

            wightedSum = that.weightedValue(cValue, 1, that.kCheckSumTotal);
            for (idx = length - 1; idx >= 0; idx--) {
                wightedSum += that.weightedValue(values[idx], length - idx + 1, that.kCheckSumTotal);
            }

            kValue = wightedSum % that.checkSumMod;
            return [cValue, kValue];
        },
        _findCharacterByValue: function (value) {
            for (var character in this.characterMap) {
                if (this.characterMap[character].value === value) {
                    return character;
                }
            }
        },
        weightedValue: function (value, index, total) {
            return (index % total || total) * value;
        },
        addPattern: function (pattern) {
            var value;

            for (var i = 0; i < pattern.length; i++) {
                value = parseInt(pattern.charAt(i), 10);
                this.pattern.push(value);
            }
        },
        characterMap: {
            "0": { "pattern": "131112", "value": 0 },
            "1": { "pattern": "111213", "value": 1 },
            "2": { "pattern": "111312", "value": 2 },
            "3": { "pattern": "111411", "value": 3 },
            "4": { "pattern": "121113", "value": 4 },
            "5": { "pattern": "121212", "value": 5 },
            "6": { "pattern": "121311", "value": 6 },
            "7": { "pattern": "111114", "value": 7 },
            "8": { "pattern": "131211", "value": 8 },
            "9": { "pattern": "141111", "value": 9 },
            "A": { "pattern": "211113", "value": 10 },
            "B": { "pattern": "211212", "value": 11 },
            "C": { "pattern": "211311", "value": 12 },
            "D": { "pattern": "221112", "value": 13 },
            "E": { "pattern": "221211", "value": 14 },
            "F": { "pattern": "231111", "value": 15 },
            "G": { "pattern": "112113", "value": 16 },
            "H": { "pattern": "112212", "value": 17 },
            "I": { "pattern": "112311", "value": 18 },
            "J": { "pattern": "122112", "value": 19 },
            "K": { "pattern": "132111", "value": 20 },
            "L": { "pattern": "111123", "value": 21 },
            "M": { "pattern": "111222", "value": 22 },
            "N": { "pattern": "111321", "value": 23 },
            "O": { "pattern": "121122", "value": 24 },
            "P": { "pattern": "131121", "value": 25 },
            "Q": { "pattern": "212112", "value": 26 },
            "R": { "pattern": "212211", "value": 27 },
            "S": { "pattern": "211122", "value": 28 },
            "T": { "pattern": "211221", "value": 29 },
            "U": { "pattern": "221121", "value": 30 },
            "V": { "pattern": "222111", "value": 31 },
            "W": { "pattern": "112122", "value": 32 },
            "X": { "pattern": "112221", "value": 33 },
            "Y": { "pattern": "122121", "value": 34 },
            "Z": { "pattern": "123111", "value": 35 },
            "-": { "pattern": "121131", "value": 36 },
            ".": { "pattern": "311112", "value": 37 },
            " ": { "pattern": "311211", "value": 38 },
            "$": { "pattern": "321111", "value": 39 },
            "/": { "pattern": "112131", "value": 40 },
            "+": { "pattern": "113121", "value": 41 },
            "%": { "pattern": "211131", "value": 42 },
            SHIFT0: { "pattern": "122211", "value": 46 },
            SHIFT1: { "pattern": "311121", "value": 45 },
            SHIFT2: { "pattern": "121221", "value": 43 },
            SHIFT3: { "pattern": "312111", "value": 44 },
            START: { "pattern": "111141" },
            TERMINATION_BAR: "1"
        }
    });

    encodings.code93extended = encodings.code93.extend(deepExtend({}, code39ExtendedBase, {
        name: "Code 93 extended",
        pushCheckSum: function () {
            var that = this,
                checkValues = that._getCheckValues(),
                value;

            that.checksum = checkValues.join("");

            for (var i = 0; i < checkValues.length; i++) {
                value = checkValues[i];
                if (that.shiftValuesAsciiCodes[value]) {
                    that.addExtended(that.shiftValuesAsciiCodes[value]);
                }
                else {
                    that.addPattern(that.characterMap[that._findCharacterByValue(value)].pattern);
                }
            }
        }
    }));

    var state128 = kendo.Class.extend({
        init: function (encoding) {
            this.encoding = encoding;
        },
        addStart: function () { },
        is: function () { },
        move: function () { },
        pushState: function () { }
    });

    var state128AB = state128.extend({
        FNC4: "FNC4",
        init: function (encoding, states) {
            var that = this;
            that.encoding = encoding;
            that.states = states;
            that._initMoves(states);
        },
        addStart: function () {
            this.encoding.addPattern(this.START);
        },
        is: function (value, index) {
            var code = value.charCodeAt(index);
            return this.isCode(code);
        },
        move: function (encodingState) {
            var that = this,
                idx = 0;

            while (!that._moves[idx].call(that, encodingState) && idx < that._moves.length) {
                idx++;
            }
        },
        pushState: function (encodingState) {
            var that = this,
                states = that.states,
                value = encodingState.value,
                maxLength = value.length,
                code;

            if (inArray("C", states) >= 0) {
                var numberMatch = value.substr(encodingState.index).match(/\d{4,}/g);
                if (numberMatch) {
                    maxLength = value.indexOf(numberMatch[0], encodingState.index);
                }
            }

            while ((code = encodingState.value.charCodeAt(encodingState.index)) >= 0 &&
                that.isCode(code) && encodingState.index < maxLength) {
                that.encoding.addPattern(that.getValue(code));
                encodingState.index++;
            }
        },
        _initMoves: function (states) {
            var that = this;
            that._moves = [];

            if (inArray(that.FNC4, states) >= 0) {
                that._moves.push(that._moveFNC);
            }

            if (inArray(that.shiftKey, states) >= 0) {
                that._moves.push(that._shiftState);
            }
            that._moves.push(that._moveState);
        },
        _moveFNC: function (encodingState) {
            if (encodingState.fnc) {
                encodingState.fnc = false;
                return encodingState.previousState == this.key;
            }
        },
        _shiftState: function (encodingState) {
            var that = this;
            if (encodingState.previousState == that.shiftKey &&
                (encodingState.index + 1 >= encodingState.value.length ||
                    that.encoding[that.shiftKey].is(encodingState.value, encodingState.index + 1))) {
                that.encoding.addPattern(that.SHIFT);
                encodingState.shifted = true;
                return true;
            }
        },
        _moveState: function () {
            this.encoding.addPattern(this.MOVE);
            return true;
        },
        SHIFT: 98
    });

    var states128 = {};

    states128.A = state128AB.extend({
        key: "A",
        shiftKey: "B",
        isCode: function (code) {
            return 0 <= code && code < 96;
        },
        getValue: function (code) {
            if (code < 32) {
                return code + 64;
            }

            return code - 32;
        },
        MOVE: 101,
        START: 103
    });

    states128.B = state128AB.extend({
        key: "B",
        shiftKey: "A",
        isCode: function (code) {
            return 32 <= code && code < 128;
        },
        getValue: function (code) {
            return code - 32;
        },
        MOVE: 100,
        START: 104
    });

    states128.C = state128.extend({
        key: "C",
        addStart: function () {
            this.encoding.addPattern(this.START);
        },
        is: function (value, index) {
            var next4 = getNext(value, index, 4);
            return (index + 4 <= value.length || value.length == 2) && numberRegex.test(next4);
        },
        move: function () {
            this.encoding.addPattern(this.MOVE);
        },
        pushState: function (encodingState) {
            var code;
            while ((code = getNext(encodingState.value, encodingState.index, 2)) &&
                numberRegex.test(code) && code.length == 2) {
                this.encoding.addPattern(parseInt(code, 10));
                encodingState.index += 2;
            }
        },
        getValue: function (code) {
            return code;
        },
        MOVE: 99,
        START: 105
    });

    states128.FNC4 = state128.extend({
        key: "FNC4",
        dependentStates: ["A", "B"],
        init: function (encoding, states) {
            this.encoding = encoding;
            this._initSubStates(states);
        },
        addStart: function (encodingState) {
            var code = encodingState.value.charCodeAt(0) - 128,
                subState = this._getSubState(code);

            this.encoding[subState].addStart();
        },
        is: function (value, index) {
            var code = value.charCodeAt(index);
            return this.isCode(code);
        },
        isCode: function (code) {
            return 128 <= code && code < 256;
        },
        pushState: function (encodingState) {
            var that = this,
                subState = that._initSubState(encodingState),
                encoding = that.encoding,
                length = subState.value.length;
            encodingState.index += length;

            if (length < 3) {
                var code;
                for (; subState.index < length; subState.index++) {
                    code = subState.value.charCodeAt(subState.index);
                    subState.state = that._getSubState(code);
                    if (subState.previousState != subState.state) {
                        subState.previousState = subState.state;
                        encoding[subState.state].move(subState);
                    }
                    encoding.addPattern(encoding[subState.state].MOVE);
                    encoding.addPattern(encoding[subState.state].getValue(code));
                }
            }
            else {
                if (subState.state != subState.previousState) {
                    encoding[subState.state].move(subState);
                }
                that._pushStart(subState);
                encoding.pushData(subState, that.subStates);
                if (encodingState.index < encodingState.value.length) {
                    that._pushStart(subState);
                }
            }

            encodingState.fnc = true;
            encodingState.state = subState.state;
        },
        _pushStart: function (subState) {
            var that = this;
            that.encoding.addPattern(that.encoding[subState.state].MOVE);
            that.encoding.addPattern(that.encoding[subState.state].MOVE);
        },
        _initSubState: function (encodingState) {
            var that = this,
                subState = {
                    value: that._getAll(encodingState.value, encodingState.index),
                    index: 0
                };
            subState.state = that._getSubState(subState.value.charCodeAt(0));
            subState.previousState = encodingState.previousState == that.key ?
                subState.state : encodingState.previousState;
            return subState;
        },
        _initSubStates: function (states) {
            var that = this;
            that.subStates = [];
            for (var i = 0; i < states.length; i++) {
                if (inArray(states[i], that.dependentStates) >= 0) {
                    that.subStates.push(states[i]);
                }
            }
        },
        _getSubState: function (code) {
            var that = this;
            for (var i = 0; i < that.subStates.length; i++) {
                if (that.encoding[that.subStates[i]].isCode(code)) {
                    return that.subStates[i];
                }
            }
        },
        _getAll: function (value, index) {
            var code,
                result = "";
            while ((code = value.charCodeAt(index++)) && this.isCode(code)) {
                result += String.fromCharCode(code - 128);
            }
            return result;
        }
    });

    states128.FNC1 = state128.extend({
        key: "FNC1",
        startState: "C",
        dependentStates: ["C", "B"],
        startAI: "(",
        endAI: ")",
        init: function (encoding, states) {
            this.encoding = encoding;
            this.states = states;
        },
        addStart: function () {
            this.encoding[this.startState].addStart();
        },
        is: function () {
            return inArray(this.key, this.states) >= 0;
        },
        pushState: function (encodingState) {
            var that = this,
                encoding = that.encoding,
                value = encodingState.value.replace(/\s/g, ""),
                regexSeparators = new RegExp("[" + that.startAI + that.endAI + "]", "g"),
                index = encodingState.index,
                subState = {
                    state: that.startState
                },
                current,
                nextStart,
                separatorLength;

            encoding.addPattern(that.START);

            while (true) {
                subState.index = 0;

                separatorLength = value.charAt(index) === that.startAI ? 2 : 0;
                current = separatorLength > 0 ? that.getBySeparator(value, index) : that.getByLength(value, index);
                if (current.ai.length) {
                    nextStart = index + separatorLength + current.id.length + current.ai.length;
                }
                else {
                    nextStart = value.indexOf(that.startAI, index + 1);
                    if (nextStart < 0) {
                        if (index + current.ai.max + current.id.length + separatorLength < value.length) {
                            throw new Error("Separators are required after variable length identifiers");
                        }
                        nextStart = value.length;
                    }
                }
                subState.value = value.substring(index, nextStart).replace(regexSeparators, "");
                that.validate(current, subState.value);

                encoding.pushData(subState, that.dependentStates);

                if (nextStart >= value.length) {
                    break;
                }

                index = nextStart;

                if (subState.state != that.startState) {
                    encoding[that.startState].move(subState);
                    subState.state = that.startState;
                }

                if (!current.ai.length) {
                    encoding.addPattern(that.START);
                }
            }
            encodingState.index = encodingState.value.length;
        },
        validate: function (current, value) {
            var code = value.substr(current.id.length),
                ai = current.ai;
            if (!ai.type && !numberRegex.test(code)) {
                throw new Error("Application identifier " + current.id + " is numeric only but contains non numeric character(s).");
            }

            if (ai.type == "alphanumeric" && !alphanumericRegex.test(code)) {
                throw new Error("Application identifier " + current.id + " is alphanumeric only but contains non alphanumeric character(s).");
            }

            if (ai.length && ai.length !== code.length) {
                throw new Error("Application identifier " + current.id + " must be " + ai.length + " characters long.");
            }

            if (ai.min && ai.min > code.length) {
                throw new Error("Application identifier " + current.id + " must be at least " + ai.min + " characters long.");
            }

            if (ai.max && ai.max < code.length) {
                throw new Error("Application identifier " + current.id + " must be at most " + ai.max + " characters long.");
            }
        },
        getByLength: function (value, index) {
            var that = this,
                id,
                ai;
            for (var i = 2; i <= 4; i++) {
                id = getNext(value, index, i);
                ai = that.getAI(id) || that.getAI(id.substring(0, id.length - 1));
                if (ai) {
                    return {
                        id: id,
                        ai: ai
                    };
                }
            }
            that.unsupportedAIError(id);
        },
        unsupportedAIError: function (id) {
            throw new Error(kendo.format("'{0}' is not a supported Application Identifier"), id);
        },
        getBySeparator: function (value, index) {
            var that = this,
                start = value.indexOf(that.startAI, index),
                end = value.indexOf(that.endAI, start),
                id = value.substring(start + 1, end),
                ai = that.getAI(id) || that.getAI(id.substr(id.length - 1));
            if (!ai) {
                that.unsupportedAIError(id);
            }

            return {
                ai: ai,
                id: id
            };
        },
        getAI: function (id) {
            var ai = this.applicationIdentifiers,
                multiKey = ai.multiKey;
            if (ai[id]) {
                return ai[id];
            }

            for (var i = 0; i < multiKey.length; i++) {
                if (multiKey[i].ids && inArray(id, multiKey[i].ids) >= 0) {
                    return multiKey[i].type;
                }
                else if (multiKey[i].ranges) {
                    var ranges = multiKey[i].ranges;
                    for (var j = 0; j < ranges.length; j++) {
                        if (ranges[j][0] <= id && id <= ranges[j][1]) {
                            return multiKey[i].type;
                        }
                    }
                }
            }
        },
        applicationIdentifiers: {
            "22": { max: 29, type: "alphanumeric" },
            "402": { length: 17 },
            "7004": { max: 4, type: "alphanumeric" },
            "242": { max: 6, type: "alphanumeric" },
            "8020": { max: 25, type: "alphanumeric" },
            "703": { min: 3, max: 30, type: "alphanumeric" },
            "8008": { min: 8, max: 12, type: "alphanumeric" },
            "253": { min: 13, max: 17, type: "alphanumeric" },
            "8003": { min: 14, max: 30, type: "alphanumeric" },
            multiKey: [{
                ids: ["15", "17", "8005", "8100"],
                ranges: [
                    [11, 13],
                    [310, 316],
                    [320, 336],
                    [340, 369]
                ],
                type: { length: 6 }
            }, {
                ids: ["240", "241", "250", "251", "400", "401", "403", "7002", "8004", "8007", "8110"],
                ranges: [[90 - 99]],
                type: { max: 30, type: "alphanumeric" }
            }, {
                ids: ["7001"],
                ranges: [[410, 414]],
                type: { length: 13 }
            }, {
                ids: ["10", "21", "254", "420", "8002"],
                type: { max: 20, type: "alphanumeric" }
            }, {
                ids: ["00", "8006", "8017", "8018"],
                type: { length: 18 }
            }, {
                ids: ["01", "02", "8001"],
                type: { length: 14 }
            }, {
                ids: ["422"],
                ranges: [
                    [424, 426]
                ],
                type: { length: 3 }
            }, {
                ids: ["20", "8102"],
                type: { length: 2 }
            }, {
                ids: ["30", "37"],
                type: { max: 8, type: "alphanumeric" }
            }, {
                ids: ["390", "392"],
                type: { max: 15, type: "alphanumeric" }
            }, {
                ids: ["421", "423"],
                type: { min: 3, max: 15, type: "alphanumeric" }
            }, {
                ids: ["391", "393"],
                type: { min: 3, max: 18, type: "alphanumeric" }
            }, {
                ids: ["7003", "8101"],
                type: { length: 10 }
            }]
        },
        START: 102
    });

    var code128Base = Encoding.extend({
        init: function (options) {
            Encoding.fn.init.call(this, options);
            this._initStates();
        },
        _initStates: function () {
            var that = this;
            for (var i = 0; i < that.states.length; i++) {
                that[that.states[i]] = new states128[that.states[i]](that, that.states);
            }
        },
        initValue: function (value, width, height) {
            var that = this;
            that.pattern = [];
            that.value = value;
            that.width = width;
            that.height = height;
            that.checkSum = 0;
            that.totalUnits = 0;
            that.index = 0;
            that.position = 1;
        },
        addData: function () {
            var that = this,
                encodingState = {
                    value: that.value,
                    index: 0,
                    state: ""
                };
            if (that.value.length === 0) {
                return;
            }

            encodingState.state =
                encodingState.previousState = that.getNextState(encodingState, that.states);

            that.addStart(encodingState);

            that.pushData(encodingState, that.states);

            that.addCheckSum();
            that.addStop();
            that.setBaseUnit();
        },
        pushData: function (encodingState, states) {
            var that = this;
            while (true) {
                that[encodingState.state].pushState(encodingState);
                if (encodingState.index >= encodingState.value.length) {
                    break;
                }

                if (!encodingState.shifted) {
                    encodingState.previousState = encodingState.state;
                    encodingState.state = that.getNextState(encodingState, states);
                    that[encodingState.state].move(encodingState);
                }
                else {
                    var temp = encodingState.state;
                    encodingState.state = encodingState.previousState;
                    encodingState.previousState = temp;
                    encodingState.shifted = false;
                }
            }
        },
        addStart: function (encodingState) {
            this[encodingState.state].addStart(encodingState);
            this.position = 1;
        },
        addCheckSum: function () {
            var that = this;

            that.checksum = that.checkSum % 103;
            that.addPattern(that.checksum);
        },
        addStop: function () {
            this.addPattern(this.STOP);
        },
        setBaseUnit: function () {
            var that = this;
            that.baseUnit = that.width / (that.totalUnits + that.quietZoneLength);
        },
        addPattern: function (code) {
            var that = this,
                pattern = that.characterMap[code].toString(),
                value;

            for (var i = 0; i < pattern.length; i++) {
                value = parseInt(pattern.charAt(i), 10);
                that.pattern.push(value);
                that.totalUnits += value;
            }
            that.checkSum += code * that.position++;
        },
        getNextState: function (encodingState, states) {
            for (var i = 0; i < states.length; i++) {
                if (this[states[i]].is(encodingState.value, encodingState.index)) {
                    return states[i];
                }
            }
            this.invalidCharacterError(encodingState.value.charAt(encodingState.index));
        },
        characterMap: [
            212222, 222122, 222221, 121223, 121322, 131222, 122213, 122312, 132212, 221213,
            221312, 231212, 112232, 122132, 122231, 113222, 123122, 123221, 223211, 221132,
            221231, 213212, 223112, 312131, 311222, 321122, 321221, 312212, 322112, 322211,
            212123, 212321, 232121, 111323, 131123, 131321, 112313, 132113, 132311, 211313,
            231113, 231311, 112133, 112331, 132131, 113123, 113321, 133121, 313121, 211331,
            231131, 213113, 213311, 213131, 311123, 311321, 331121, 312113, 312311, 332111,
            314111, 221411, 431111, 111224, 111422, 121124, 121421, 141122, 141221, 112214,
            112412, 122114, 122411, 142112, 142211, 241211, 221114, 413111, 241112, 134111,
            111242, 121142, 121241, 114212, 124112, 124211, 411212, 421112, 421211, 212141,
            214121, 412121, 111143, 111341, 131141, 114113, 114311, 411113, 411311, 113141,
            114131, 311141, 411131, 211412, 211214, 211232, 2331112
        ],
        STOP: 106
    });

    encodings.code128a = code128Base.extend({
        name: "Code 128 A",
        states: ["A"]
    });


    encodings.code128b = code128Base.extend({
        name: "Code 128 B",
        states: ["B"]
    });

    encodings.code128c = code128Base.extend({
        name: "Code 128 C",
        states: ["C"]
    });

    encodings.code128 = code128Base.extend({
        name: "Code 128",
        states: ["C", "B", "A", "FNC4"]
    });

    encodings["gs1-128"] = code128Base.extend({
        name: "Code GS1-128",
        states: ["FNC1", "C", "B"]
    });

    var msiBase = Encoding.extend({
        initValue: function (value, width) {
            var that = this;
            that.pattern = [];
            that.value = value;
            that.checkSumLength = 0;
            that.width = width;
        },
        setBaseUnit: function () {
            var that = this,
                startStopLength = 7;

            that.baseUnit = that.width /
                    (12 * (that.value.length + that.checkSumLength) + that.quietZoneLength + startStopLength);
        },
        addData: function () {
            var that = this,
                value = that.value;
            that.addPattern(that.START);

            for (var i = 0; i < value.length; i++) {
                that.addCharacter(value.charAt(i));
            }

            if (that.options.addCheckSum) {
                that.addCheckSum();
            }

            that.addPattern(that.STOP);
            that.setBaseUnit();
        },
        addCharacter: function (character) {
            var that = this,
                pattern = that.characterMap[character];
            if (!pattern) {
                that.invalidCharacterError(character);
            }
            that.addPattern(pattern);
        },
        addPattern: function (pattern) {
            for (var i = 0; i < pattern.length; i++) {
                this.pattern.push(parseInt(pattern.charAt(i), 10));
            }
        },
        addCheckSum: function () {
            var that = this,
                checkSumFunction = that.checkSums[that.checkSumType],
                checkValues;

            checkValues = checkSumFunction.call(that.checkSums, that.value);

            that.checksum = checkValues.join("");
            for (var i = 0; i < checkValues.length; i++) {
                that.checkSumLength++;
                that.addPattern(that.characterMap[checkValues[i]]);
            }
        },
        checkSums: {
            Modulo10: function (value) {
                var checkValues = [0, ""],
                odd = value.length % 2,
                idx,
                evenSum,
                oddSum;

                for (idx = 0; idx < value.length; idx++) {
                    checkValues[(idx + odd) % 2] += parseInt(value.charAt(idx), 10);
                }

                oddSum = checkValues[0];
                evenSum = (checkValues[1] * 2).toString();

                for (idx = 0; idx < evenSum.length; idx++) {
                    oddSum += parseInt(evenSum.charAt(idx), 10);
                }

                return [(10 - (oddSum % 10)) % 10];
            },
            Modulo11: function (value) {
                var weightedSum = 0,
                    mod = 11,
                    length = value.length,
                    weight,
                    checkValue;

                for (var i = 0; i < length; i++) {
                    weight = ((length - i) % 6 || 6) + 1;
                    weightedSum += weight * value.charAt(i);
                }
                checkValue = (mod - weightedSum % mod) % mod;
                if (checkValue != 10) {
                    return [checkValue];
                }
                return [1, 0];
            },
            Modulo11Modulo10: function (value) {
                var checkValues = this.Modulo11(value),
                    mod11Value;
                mod11Value = value + checkValues[0];

                return checkValues.concat(this.Modulo10(mod11Value));
            },
            Modulo10Modulo10: function (value) {
                var checkValues = this.Modulo10(value),
                    mod10Value;
                mod10Value = value + checkValues[0];

                return checkValues.concat(this.Modulo10(mod10Value));
            }
        },
        characterMap: ["12121212", "12121221", "12122112", "12122121", "12211212", "12211221", "12212112", "12212121", "21121212", "21121221"],
        START: "21",
        STOP: "121",
        checkSumType: ""
    });

    encodings.msimod10 = msiBase.extend({
        name: "MSI Modulo10",
        checkSumType: "Modulo10"
    });

    encodings.msimod11 = msiBase.extend({
        name: "MSI Modulo11",
        checkSumType: "Modulo11"
    });

    encodings.msimod1110 = msiBase.extend({
        name: "MSI Modulo11 Modulo10",
        checkSumType: "Modulo11Modulo10"
    });

    encodings.msimod1010 = msiBase.extend({
        name: "MSI Modulo10 Modulo10",
        checkSumType: "Modulo10Modulo10"
    });

    encodings.code11 = Encoding.extend({
        name: "Code 11",
        cCheckSumTotal: 10,
        kCheckSumTotal: 9,
        kCheckSumMinLength: 10,
        checkSumMod: 11,
        DASH_VALUE: 10,
        DASH: "-",
        START: "112211",
        STOP: "11221",
        initValue: function (value, width) {
            var that = this;
            that.pattern = [];
            that.value = value;
            that.width = width;
            that.totalUnits = 0;
        },
        addData: function () {
            var that = this;
            var value = that.value;
            that.addPattern(that.START);

            for (var i = 0; i < value.length; i++) {
                that.addCharacter(value.charAt(i));
            }

            if (that.options.addCheckSum) {
                that.addCheckSum();
            }

            that.addPattern(that.STOP);
            that.setBaseUnit();
        },
        setBaseUnit: function () {
            var that = this;
            that.baseUnit = that.width / (that.totalUnits + that.quietZoneLength);
        },
        addCheckSum: function () {
            var that = this,
                value = that.value,
                length = value.length,
                cValue;

            cValue = that.getWeightedSum(value, length, that.cCheckSumTotal) % that.checkSumMod;
            that.checksum = cValue + "";
            that.addPattern(that.characterMap[cValue]);

            length++;
            if (length >= that.kCheckSumMinLength) {
                var kValue = (cValue + that.getWeightedSum(value, length, that.kCheckSumTotal)) % that.checkSumMod;
                that.checksum += kValue;
                that.addPattern(that.characterMap[kValue]);
            }
        },
        getWeightedSum: function (value, length, total) {
            var weightedSum = 0;
            for (var i = 0; i < value.length; i++) {
                weightedSum += this.weightedValue(this.getValue(value.charAt(i)), length, i, total);
            }

            return weightedSum;
        },
        weightedValue: function (value, length, index, total) {
            var weight = (length - index) % total || total;
            return weight * value;
        },
        getValue: function (character) {
            var that = this;
            if (!isNaN(character)) {
                return parseInt(character, 10);
            }
            else if (character !== that.DASH) {
                that.invalidCharacterError(character);
            }
            return that.DASH_VALUE;
        },
        addCharacter: function (character) {
            var that = this,
                value = that.getValue(character),
                pattern = that.characterMap[value];
            that.addPattern(pattern);
        },
        addPattern: function (pattern) {
            var value;
            for (var i = 0; i < pattern.length; i++) {
                value = parseInt(pattern.charAt(i), 10);
                this.pattern.push(value);
                this.totalUnits += value;
            }
        },
        characterMap: ["111121", "211121", "121121", "221111", "112121", "212111", "122111", "111221", "211211", "211111", "112111"],
        options: {
            addCheckSum: true
        }
    });

    encodings.postnet = Encoding.extend({
        name: "Postnet",
        START: "2",
        VALID_CODE_LENGTHS: [5, 9, 11],
        DIGIT_SEPARATOR: "-",
        initValue: function (value, width, height) {
            var that = this;
            that.height = height;
            that.width = width;
            that.baseHeight = height / 2;
            that.value = value.replace(new RegExp(that.DIGIT_SEPARATOR, "g"), "");
            that.pattern = [];
            that.validate(that.value);
            that.checkSum = 0;
            that.setBaseUnit();
        },
        addData: function () {
            var that = this,
                value = that.value;
            that.addPattern(that.START);

            for (var i = 0; i < value.length; i++) {
                that.addCharacter(value.charAt(i));
            }

            if (that.options.addCheckSum) {
                that.addCheckSum();
            }

            that.addPattern(that.START);
            that.pattern.pop();
        },
        addCharacter: function (character) {
            var that = this,
                pattern = that.characterMap[character];
            that.checkSum += parseInt(character, 10);
            that.addPattern(pattern);
        },
        addCheckSum: function () {
            var that = this;
            that.checksum = (10 - (that.checkSum % 10)) % 10;
            that.addCharacter(that.checksum);
        },
        setBaseUnit: function () {
            var that = this,
                startStopLength = 3;
            that.baseUnit = that.width / ((that.value.length + 1) * 10 + startStopLength + that.quietZoneLength);
        },
        validate: function (value) {
            var that = this;

            if (!numberRegex.test(value)) {
                that.invalidCharacterError(value.match(/[^0-9]/)[0]);
            }
            if (inArray(value.length, that.VALID_CODE_LENGTHS) < 0) {
                throw new Error("Invalid value length. Valid lengths for the Postnet symbology are " + that.VALID_CODE_LENGTHS.join(","));
            }
        },
        addPattern: function (pattern) {
            var that = this,
                y1;
            for (var i = 0; i < pattern.length; i++) {
                y1 = that.height - that.baseHeight * pattern.charAt(i);
                that.pattern.push({ width: 1, y1: y1, y2: that.height });
                that.pattern.push(1);
            }
        },
        characterMap: ["22111", "11122", "11212", "11221", "12112", "12121", "12211", "21112", "21121", "21211"]
    });

    encodings.ean13 = Encoding.extend({
        initValue: function (value, width, height) {
            value += "";

            if (value.length != 12 || /\D/.test(value)) {
                throw new Error('The value of the "EAN13" encoding should be 12 symbols');
            }

            var that = this;
            that.pattern = [];
            that.options.height = height;
            that.baseUnit = width / (95 + that.quietZoneLength);
            that.value = value;
            that.checksum = that.calculateChecksum();
            that.leftKey = value[0];
            that.leftPart = value.substr(1, 6);
            that.rightPart = value.substr(7) + that.checksum;
        },
        addData: function () {
            var that = this;
            that.addPieces(that.characterMap.start);
            that.addSide(that.leftPart, that.leftKey);
            that.addPieces(that.characterMap.middle);
            that.addSide(that.rightPart);
            that.addPieces(that.characterMap.start);
        },
        addSide: function (leftPart, key) {
            var that = this;
            for (var i = 0; i < leftPart.length; i++) {
                if (key && parseInt(that.keyTable[key].charAt(i), 10)) {
                    that.addPieces(Array.prototype.slice.call(that.characterMap.digits[leftPart.charAt(i)]).reverse(), true);
                } else {
                    that.addPieces(that.characterMap.digits[leftPart.charAt(i)], true);
                }
            }
        },
        addPieces: function (arrToAdd, limitedHeight) {
            var that = this;
            for (var i = 0; i < arrToAdd.length; i++) {
                if (limitedHeight) {
                    that.pattern.push({
                        y1: 0,
                        y2: that.options.height * 0.95,
                        width: arrToAdd[i]
                    });
                } else {
                    that.pattern.push(arrToAdd[i]);
                }
            }
        },
        calculateChecksum: function () {
            var odd = 0,
                even = 0,
                value = this.value.split("").reverse().join("");
            for (var i = 0; i < value.length; i++) {
                if (i % 2) {
                    even += parseInt(value.charAt(i), 10);
                }
                else {
                    odd += parseInt(value.charAt(i), 10);
                }
            }
            var checksum = (10 - ((3 * odd + even) % 10)) % 10;
            return checksum;
        },
        keyTable: [
            '000000',
            '001011',
            '001101',
            '001110',
            '010011',
            '011001',
            '011100',
            '010101',
            '010110',
            '011010'
        ],
        characterMap: {
            digits: [
                [3, 2, 1, 1],
                [2, 2, 2, 1],
                [2, 1, 2, 2],
                [1, 4, 1, 1],
                [1, 1, 3, 2],
                [1, 2, 3, 1],
                [1, 1, 1, 4],
                [1, 3, 1, 2],
                [1, 2, 1, 3],
                [3, 1, 1, 2]
            ],
            start: [1, 1, 1],
            middle: [1, 1, 1, 1, 1]
        }
    });

    encodings.ean8 = encodings.ean13.extend({
        initValue: function (value, width, height) {
            var that = this;
            if (value.length != 7 || /\D/.test(value)) {
                throw new Error('Invalid value provided');
            }
            that.value = value;
            that.options.height = height;
            that.checksum = that.calculateChecksum(that.value);
            that.leftPart = that.value.substr(0, 4);
            that.rightPart = that.value.substr(4) + that.checksum;
            that.pattern = [];
            that.baseUnit = width / (67 + that.quietZoneLength);
        }
    });

    var Barcode = Widget.extend({
        init: function (element, options) {
            var that = this;
            Widget.fn.init.call(that, element, options);
            that.element = $(element);
            that.wrapper = that.element;
            that.element.addClass("k-barcode").css("display", "block");
            that.surfaceWrap = $("<div />").css("position", "relative").appendTo(this.element);
            that.surface = draw.Surface.create(that.surfaceWrap, {
                type: that.options.renderAs
            });
            that.setOptions(options);
        },

        setOptions: function (options) {
            var that = this;
            that.type = (options.type || that.options.type).toLowerCase();
            if (that.type == "upca") { //extend instead
                that.type = "ean13";
                options.value = '0' + options.value;
            }
            if (that.type == "upce") {
                that.type = "ean8";
                options.value = '0' + options.value;
            }
            if (!encodings[that.type]) {
                throw new Error('Encoding ' + that.type + 'is not supported.');
            }
            that.encoding = new encodings[that.type]();

            that.options = extend(true, that.options, options);
            if (!defined(options.value)) {
                return;
            }
            that.redraw();
        },

        redraw: function () {
            var size = this._getSize();
            this.surfaceWrap.css({
                width: size.width,
                height: size.height
            });
            this.surface.clear();

            this.createVisual();
            this.surface.draw(this.visual);
        },

        getSize: function () {
            return kendo.dimensions(this.element);
        },

        _resize: function () {
            this.redraw();
        },

        createVisual: function () {
            this.visual = this._render();
        },

        _render: function () {
            var that = this,
                options = that.options,
                value = options.value,
                textOptions = options.text,
                textMargin = dataviz.getSpacing(textOptions.margin),
                size = that._getSize(),
                border = options.border || {},
                encoding = that.encoding,
                contentBox = Box2D(0, 0, size.width, size.height).unpad(border.width).unpad(options.padding),
                barHeight = contentBox.height(),
                result, textToDisplay,
                textHeight;

            var visual = new draw.Group();

            that.contentBox = contentBox;
            visual.append(that._getBackground(size));

            if (textOptions.visible) {
                textToDisplay = value;
                if (options.checksum && defined(encoding.checksum)) {
                    textToDisplay += " " + encoding.checksum;
                }

                var text = that._getText(textToDisplay);
                textHeight = text.bbox().height();
                barHeight -= textHeight + textMargin.top + textMargin.bottom;

                visual.append(text);
            }

            result = encoding.encode(value, contentBox.width(), barHeight);

            that.barHeight = barHeight;

            visual.append(this._getBands(result.pattern, result.baseUnit));

            return visual;
        },

        exportVisual: function () {
            return this._render();
        },

        _getSize: function () {
            var that = this,
                element = that.element,
                size = new geom.Size(DEFAULT_WIDTH, DEFAULT_HEIGHT);

            if (element.width() > 0) {
                size.width = element.width();
            }
            if (element.height() > 0) {
                size.height = element.height();
            }
            if (that.options.width) {
                size.width = that.options.width;
            }
            if (that.options.height) {
                size.height = that.options.height;
            }

            return size;
        },

        value: function (value) {
            var that = this;
            if (!defined(value)) {
                return that.options.value;
            }
            that.options.value = value + '';
            that.redraw();
        },

        _getBands: function (pattern, baseUnit) {
            var that = this,
                contentBox = that.contentBox,
                position = contentBox.x1,
                step,
                item;

            var group = new draw.Group();
            for (var i = 0; i < pattern.length; i++) {
                item = isPlainObject(pattern[i]) ? pattern[i] : {
                    width: pattern[i],
                    y1: 0,
                    y2: that.barHeight
                };

                step = item.width * baseUnit;

                if (i % 2) {
                    var rect = geom.Rect.fromPoints(
                        new geom.Point(position, item.y1 + contentBox.y1),
                        new geom.Point(position + step, item.y2 + contentBox.y1)
                    );

                    var path = draw.Path.fromRect(rect, {
                        fill: {
                            color: that.options.color
                        },
                        stroke: null
                    });

                    group.append(path);
                }

                position += step;
            }

            return group;
        },

        _getBackground: function (size) {
            var that = this,
                options = that.options,
                border = options.border || {};

            var box = Box2D(0, 0, size.width, size.height).unpad(border.width / 2);
            var path = draw.Path.fromRect(box.toRect(), {
                fill: {
                    color: options.background
                },
                stroke: {
                    color: border.width ? border.color : "",
                    width: border.width,
                    dashType: border.dashType
                }
            });

            return path;
        },

        _getText: function (value) {
            var that = this,
                textOptions = that.options.text,
                text = new TextBox(value, {
                    font: textOptions.font,
                    color: textOptions.color,
                    align: "center",
                    vAlign: "bottom",
                    margin: textOptions.margin
                });

            text.reflow(that.contentBox);
            text.renderVisual();

            return text.visual;
        },

        options: {
            name: "Barcode",
            renderAs: "svg",
            value: "",
            type: "code39",
            checksum: false,
            width: 0,
            height: 0,
            color: "black",
            background: "white",
            text: {
                visible: true,
                font: "16px Consolas, Monaco, Sans Mono, monospace, sans-serif",
                color: "black",
                margin: {
                    top: 0,
                    bottom: 0,
                    left: 0,
                    right: 0
                }
            },
            border: {
                width: 0,
                dashType: "solid",
                color: "black"
            },
            padding: {
                top: 0,
                bottom: 0,
                left: 0,
                right: 0
            }
        }
    });
    dataviz.ExportMixin.extend(Barcode.fn);

    dataviz.ui.plugin(Barcode);

    kendo.deepExtend(dataviz, {
        encodings: encodings,
        Encoding: Encoding
    });

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        extend = $.extend,
        geom = kendo.geometry,
        draw = kendo.drawing,
        dataviz = kendo.dataviz,
        Widget = kendo.ui.Widget,
        Box2D = dataviz.Box2D,
        terminator = "0000",
        NUMERIC = "numeric",
        ALPHA_NUMERIC = "alphanumeric",
        BYTE = "byte",
        powersOfTwo = { "1": 0 },
        powersOfTwoResult = { "0": 1 },
        generatorPolynomials = [[1, 0], [1, 25, 0]],
        irregularAlignmentPatternsStartDistance = { 15: 20, 16: 20, 18: 24, 19: 24, 22: 20, 24: 22, 26: 24, 28: 20, 30: 20, 31: 24, 32: 28, 33: 24, 36: 18, 37: 22, 39: 20, 40: 24 },
        versionsCodewordsInformation = [{ L: { groups: [[1, 19]], totalDataCodewords: 19, errorCodewordsPerBlock: 7 }, M: { groups: [[1, 16]], totalDataCodewords: 16, errorCodewordsPerBlock: 10 }, Q: { groups: [[1, 13]], totalDataCodewords: 13, errorCodewordsPerBlock: 13 }, H: { groups: [[1, 9]], totalDataCodewords: 9, errorCodewordsPerBlock: 17} }, { L: { groups: [[1, 34]], totalDataCodewords: 34, errorCodewordsPerBlock: 10 }, M: { groups: [[1, 28]], totalDataCodewords: 28, errorCodewordsPerBlock: 16 }, Q: { groups: [[1, 22]], totalDataCodewords: 22, errorCodewordsPerBlock: 22 }, H: { groups: [[1, 16]], totalDataCodewords: 16, errorCodewordsPerBlock: 28} }, { L: { groups: [[1, 55]], totalDataCodewords: 55, errorCodewordsPerBlock: 15 }, M: { groups: [[1, 44]], totalDataCodewords: 44, errorCodewordsPerBlock: 26 }, Q: { groups: [[2, 17]], totalDataCodewords: 34, errorCodewordsPerBlock: 18 }, H: { groups: [[2, 13]], totalDataCodewords: 26, errorCodewordsPerBlock: 22} }, { L: { groups: [[1, 80]], totalDataCodewords: 80, errorCodewordsPerBlock: 20 }, M: { groups: [[2, 32]], totalDataCodewords: 64, errorCodewordsPerBlock: 18 }, Q: { groups: [[2, 24]], totalDataCodewords: 48, errorCodewordsPerBlock: 26 }, H: { groups: [[4, 9]], totalDataCodewords: 36, errorCodewordsPerBlock: 16} }, { L: { groups: [[1, 108]], totalDataCodewords: 108, errorCodewordsPerBlock: 26 }, M: { groups: [[2, 43]], totalDataCodewords: 86, errorCodewordsPerBlock: 24 }, Q: { groups: [[2, 15], [2, 16]], totalDataCodewords: 62, errorCodewordsPerBlock: 18 }, H: { groups: [[2, 11], [2, 12]], totalDataCodewords: 46, errorCodewordsPerBlock: 22} }, { L: { groups: [[2, 68]], totalDataCodewords: 136, errorCodewordsPerBlock: 18 }, M: { groups: [[4, 27]], totalDataCodewords: 108, errorCodewordsPerBlock: 16 }, Q: { groups: [[4, 19]], totalDataCodewords: 76, errorCodewordsPerBlock: 24 }, H: { groups: [[4, 15]], totalDataCodewords: 60, errorCodewordsPerBlock: 28} }, { L: { groups: [[2, 78]], totalDataCodewords: 156, errorCodewordsPerBlock: 20 }, M: { groups: [[4, 31]], totalDataCodewords: 124, errorCodewordsPerBlock: 18 }, Q: { groups: [[2, 14], [4, 15]], totalDataCodewords: 88, errorCodewordsPerBlock: 18 }, H: { groups: [[4, 13], [1, 14]], totalDataCodewords: 66, errorCodewordsPerBlock: 26} }, { L: { groups: [[2, 97]], totalDataCodewords: 194, errorCodewordsPerBlock: 24 }, M: { groups: [[2, 38], [2, 39]], totalDataCodewords: 154, errorCodewordsPerBlock: 22 }, Q: { groups: [[4, 18], [2, 19]], totalDataCodewords: 110, errorCodewordsPerBlock: 22 }, H: { groups: [[4, 14], [2, 15]], totalDataCodewords: 86, errorCodewordsPerBlock: 26} }, { L: { groups: [[2, 116]], totalDataCodewords: 232, errorCodewordsPerBlock: 30 }, M: { groups: [[3, 36], [2, 37]], totalDataCodewords: 182, errorCodewordsPerBlock: 22 }, Q: { groups: [[4, 16], [4, 17]], totalDataCodewords: 132, errorCodewordsPerBlock: 20 }, H: { groups: [[4, 12], [4, 13]], totalDataCodewords: 100, errorCodewordsPerBlock: 24} }, { L: { groups: [[2, 68], [2, 69]], totalDataCodewords: 274, errorCodewordsPerBlock: 18 }, M: { groups: [[4, 43], [1, 44]], totalDataCodewords: 216, errorCodewordsPerBlock: 26 }, Q: { groups: [[6, 19], [2, 20]], totalDataCodewords: 154, errorCodewordsPerBlock: 24 }, H: { groups: [[6, 15], [2, 16]], totalDataCodewords: 122, errorCodewordsPerBlock: 28} }, { L: { groups: [[4, 81]], totalDataCodewords: 324, errorCodewordsPerBlock: 20 }, M: { groups: [[1, 50], [4, 51]], totalDataCodewords: 254, errorCodewordsPerBlock: 30 }, Q: { groups: [[4, 22], [4, 23]], totalDataCodewords: 180, errorCodewordsPerBlock: 28 }, H: { groups: [[3, 12], [8, 13]], totalDataCodewords: 140, errorCodewordsPerBlock: 24} }, { L: { groups: [[2, 92], [2, 93]], totalDataCodewords: 370, errorCodewordsPerBlock: 24 }, M: { groups: [[6, 36], [2, 37]], totalDataCodewords: 290, errorCodewordsPerBlock: 22 }, Q: { groups: [[4, 20], [6, 21]], totalDataCodewords: 206, errorCodewordsPerBlock: 26 }, H: { groups: [[7, 14], [4, 15]], totalDataCodewords: 158, errorCodewordsPerBlock: 28} }, { L: { groups: [[4, 107]], totalDataCodewords: 428, errorCodewordsPerBlock: 26 }, M: { groups: [[8, 37], [1, 38]], totalDataCodewords: 334, errorCodewordsPerBlock: 22 }, Q: { groups: [[8, 20], [4, 21]], totalDataCodewords: 244, errorCodewordsPerBlock: 24 }, H: { groups: [[12, 11], [4, 12]], totalDataCodewords: 180, errorCodewordsPerBlock: 22} }, { L: { groups: [[3, 115], [1, 116]], totalDataCodewords: 461, errorCodewordsPerBlock: 30 }, M: { groups: [[4, 40], [5, 41]], totalDataCodewords: 365, errorCodewordsPerBlock: 24 }, Q: { groups: [[11, 16], [5, 17]], totalDataCodewords: 261, errorCodewordsPerBlock: 20 }, H: { groups: [[11, 12], [5, 13]], totalDataCodewords: 197, errorCodewordsPerBlock: 24} }, { L: { groups: [[5, 87], [1, 88]], totalDataCodewords: 523, errorCodewordsPerBlock: 22 }, M: { groups: [[5, 41], [5, 42]], totalDataCodewords: 415, errorCodewordsPerBlock: 24 }, Q: { groups: [[5, 24], [7, 25]], totalDataCodewords: 295, errorCodewordsPerBlock: 30 }, H: { groups: [[11, 12], [7, 13]], totalDataCodewords: 223, errorCodewordsPerBlock: 24} }, { L: { groups: [[5, 98], [1, 99]], totalDataCodewords: 589, errorCodewordsPerBlock: 24 }, M: { groups: [[7, 45], [3, 46]], totalDataCodewords: 453, errorCodewordsPerBlock: 28 }, Q: { groups: [[15, 19], [2, 20]], totalDataCodewords: 325, errorCodewordsPerBlock: 24 }, H: { groups: [[3, 15], [13, 16]], totalDataCodewords: 253, errorCodewordsPerBlock: 30} }, { L: { groups: [[1, 107], [5, 108]], totalDataCodewords: 647, errorCodewordsPerBlock: 28 }, M: { groups: [[10, 46], [1, 47]], totalDataCodewords: 507, errorCodewordsPerBlock: 28 }, Q: { groups: [[1, 22], [15, 23]], totalDataCodewords: 367, errorCodewordsPerBlock: 28 }, H: { groups: [[2, 14], [17, 15]], totalDataCodewords: 283, errorCodewordsPerBlock: 28} }, { L: { groups: [[5, 120], [1, 121]], totalDataCodewords: 721, errorCodewordsPerBlock: 30 }, M: { groups: [[9, 43], [4, 44]], totalDataCodewords: 563, errorCodewordsPerBlock: 26 }, Q: { groups: [[17, 22], [1, 23]], totalDataCodewords: 397, errorCodewordsPerBlock: 28 }, H: { groups: [[2, 14], [19, 15]], totalDataCodewords: 313, errorCodewordsPerBlock: 28} }, { L: { groups: [[3, 113], [4, 114]], totalDataCodewords: 795, errorCodewordsPerBlock: 28 }, M: { groups: [[3, 44], [11, 45]], totalDataCodewords: 627, errorCodewordsPerBlock: 26 }, Q: { groups: [[17, 21], [4, 22]], totalDataCodewords: 445, errorCodewordsPerBlock: 26 }, H: { groups: [[9, 13], [16, 14]], totalDataCodewords: 341, errorCodewordsPerBlock: 26} }, { L: { groups: [[3, 107], [5, 108]], totalDataCodewords: 861, errorCodewordsPerBlock: 28 }, M: { groups: [[3, 41], [13, 42]], totalDataCodewords: 669, errorCodewordsPerBlock: 26 }, Q: { groups: [[15, 24], [5, 25]], totalDataCodewords: 485, errorCodewordsPerBlock: 30 }, H: { groups: [[15, 15], [10, 16]], totalDataCodewords: 385, errorCodewordsPerBlock: 28} }, { L: { groups: [[4, 116], [4, 117]], totalDataCodewords: 932, errorCodewordsPerBlock: 28 }, M: { groups: [[17, 42]], totalDataCodewords: 714, errorCodewordsPerBlock: 26 }, Q: { groups: [[17, 22], [6, 23]], totalDataCodewords: 512, errorCodewordsPerBlock: 28 }, H: { groups: [[19, 16], [6, 17]], totalDataCodewords: 406, errorCodewordsPerBlock: 30} }, { L: { groups: [[2, 111], [7, 112]], totalDataCodewords: 1006, errorCodewordsPerBlock: 28 }, M: { groups: [[17, 46]], totalDataCodewords: 782, errorCodewordsPerBlock: 28 }, Q: { groups: [[7, 24], [16, 25]], totalDataCodewords: 568, errorCodewordsPerBlock: 30 }, H: { groups: [[34, 13]], totalDataCodewords: 442, errorCodewordsPerBlock: 24} }, { L: { groups: [[4, 121], [5, 122]], totalDataCodewords: 1094, errorCodewordsPerBlock: 30 }, M: { groups: [[4, 47], [14, 48]], totalDataCodewords: 860, errorCodewordsPerBlock: 28 }, Q: { groups: [[11, 24], [14, 25]], totalDataCodewords: 614, errorCodewordsPerBlock: 30 }, H: { groups: [[16, 15], [14, 16]], totalDataCodewords: 464, errorCodewordsPerBlock: 30} }, { L: { groups: [[6, 117], [4, 118]], totalDataCodewords: 1174, errorCodewordsPerBlock: 30 }, M: { groups: [[6, 45], [14, 46]], totalDataCodewords: 914, errorCodewordsPerBlock: 28 }, Q: { groups: [[11, 24], [16, 25]], totalDataCodewords: 664, errorCodewordsPerBlock: 30 }, H: { groups: [[30, 16], [2, 17]], totalDataCodewords: 514, errorCodewordsPerBlock: 30} }, { L: { groups: [[8, 106], [4, 107]], totalDataCodewords: 1276, errorCodewordsPerBlock: 26 }, M: { groups: [[8, 47], [13, 48]], totalDataCodewords: 1000, errorCodewordsPerBlock: 28 }, Q: { groups: [[7, 24], [22, 25]], totalDataCodewords: 718, errorCodewordsPerBlock: 30 }, H: { groups: [[22, 15], [13, 16]], totalDataCodewords: 538, errorCodewordsPerBlock: 30} }, { L: { groups: [[10, 114], [2, 115]], totalDataCodewords: 1370, errorCodewordsPerBlock: 28 }, M: { groups: [[19, 46], [4, 47]], totalDataCodewords: 1062, errorCodewordsPerBlock: 28 }, Q: { groups: [[28, 22], [6, 23]], totalDataCodewords: 754, errorCodewordsPerBlock: 28 }, H: { groups: [[33, 16], [4, 17]], totalDataCodewords: 596, errorCodewordsPerBlock: 30} }, { L: { groups: [[8, 122], [4, 123]], totalDataCodewords: 1468, errorCodewordsPerBlock: 30 }, M: { groups: [[22, 45], [3, 46]], totalDataCodewords: 1128, errorCodewordsPerBlock: 28 }, Q: { groups: [[8, 23], [26, 24]], totalDataCodewords: 808, errorCodewordsPerBlock: 30 }, H: { groups: [[12, 15], [28, 16]], totalDataCodewords: 628, errorCodewordsPerBlock: 30} }, { L: { groups: [[3, 117], [10, 118]], totalDataCodewords: 1531, errorCodewordsPerBlock: 30 }, M: { groups: [[3, 45], [23, 46]], totalDataCodewords: 1193, errorCodewordsPerBlock: 28 }, Q: { groups: [[4, 24], [31, 25]], totalDataCodewords: 871, errorCodewordsPerBlock: 30 }, H: { groups: [[11, 15], [31, 16]], totalDataCodewords: 661, errorCodewordsPerBlock: 30} }, { L: { groups: [[7, 116], [7, 117]], totalDataCodewords: 1631, errorCodewordsPerBlock: 30 }, M: { groups: [[21, 45], [7, 46]], totalDataCodewords: 1267, errorCodewordsPerBlock: 28 }, Q: { groups: [[1, 23], [37, 24]], totalDataCodewords: 911, errorCodewordsPerBlock: 30 }, H: { groups: [[19, 15], [26, 16]], totalDataCodewords: 701, errorCodewordsPerBlock: 30} }, { L: { groups: [[5, 115], [10, 116]], totalDataCodewords: 1735, errorCodewordsPerBlock: 30 }, M: { groups: [[19, 47], [10, 48]], totalDataCodewords: 1373, errorCodewordsPerBlock: 28 }, Q: { groups: [[15, 24], [25, 25]], totalDataCodewords: 985, errorCodewordsPerBlock: 30 }, H: { groups: [[23, 15], [25, 16]], totalDataCodewords: 745, errorCodewordsPerBlock: 30} }, { L: { groups: [[13, 115], [3, 116]], totalDataCodewords: 1843, errorCodewordsPerBlock: 30 }, M: { groups: [[2, 46], [29, 47]], totalDataCodewords: 1455, errorCodewordsPerBlock: 28 }, Q: { groups: [[42, 24], [1, 25]], totalDataCodewords: 1033, errorCodewordsPerBlock: 30 }, H: { groups: [[23, 15], [28, 16]], totalDataCodewords: 793, errorCodewordsPerBlock: 30} }, { L: { groups: [[17, 115]], totalDataCodewords: 1955, errorCodewordsPerBlock: 30 }, M: { groups: [[10, 46], [23, 47]], totalDataCodewords: 1541, errorCodewordsPerBlock: 28 }, Q: { groups: [[10, 24], [35, 25]], totalDataCodewords: 1115, errorCodewordsPerBlock: 30 }, H: { groups: [[19, 15], [35, 16]], totalDataCodewords: 845, errorCodewordsPerBlock: 30} }, { L: { groups: [[17, 115], [1, 116]], totalDataCodewords: 2071, errorCodewordsPerBlock: 30 }, M: { groups: [[14, 46], [21, 47]], totalDataCodewords: 1631, errorCodewordsPerBlock: 28 }, Q: { groups: [[29, 24], [19, 25]], totalDataCodewords: 1171, errorCodewordsPerBlock: 30 }, H: { groups: [[11, 15], [46, 16]], totalDataCodewords: 901, errorCodewordsPerBlock: 30} }, { L: { groups: [[13, 115], [6, 116]], totalDataCodewords: 2191, errorCodewordsPerBlock: 30 }, M: { groups: [[14, 46], [23, 47]], totalDataCodewords: 1725, errorCodewordsPerBlock: 28 }, Q: { groups: [[44, 24], [7, 25]], totalDataCodewords: 1231, errorCodewordsPerBlock: 30 }, H: { groups: [[59, 16], [1, 17]], totalDataCodewords: 961, errorCodewordsPerBlock: 30} }, { L: { groups: [[12, 121], [7, 122]], totalDataCodewords: 2306, errorCodewordsPerBlock: 30 }, M: { groups: [[12, 47], [26, 48]], totalDataCodewords: 1812, errorCodewordsPerBlock: 28 }, Q: { groups: [[39, 24], [14, 25]], totalDataCodewords: 1286, errorCodewordsPerBlock: 30 }, H: { groups: [[22, 15], [41, 16]], totalDataCodewords: 986, errorCodewordsPerBlock: 30} }, { L: { groups: [[6, 121], [14, 122]], totalDataCodewords: 2434, errorCodewordsPerBlock: 30 }, M: { groups: [[6, 47], [34, 48]], totalDataCodewords: 1914, errorCodewordsPerBlock: 28 }, Q: { groups: [[46, 24], [10, 25]], totalDataCodewords: 1354, errorCodewordsPerBlock: 30 }, H: { groups: [[2, 15], [64, 16]], totalDataCodewords: 1054, errorCodewordsPerBlock: 30} }, { L: { groups: [[17, 122], [4, 123]], totalDataCodewords: 2566, errorCodewordsPerBlock: 30 }, M: { groups: [[29, 46], [14, 47]], totalDataCodewords: 1992, errorCodewordsPerBlock: 28 }, Q: { groups: [[49, 24], [10, 25]], totalDataCodewords: 1426, errorCodewordsPerBlock: 30 }, H: { groups: [[24, 15], [46, 16]], totalDataCodewords: 1096, errorCodewordsPerBlock: 30} }, { L: { groups: [[4, 122], [18, 123]], totalDataCodewords: 2702, errorCodewordsPerBlock: 30 }, M: { groups: [[13, 46], [32, 47]], totalDataCodewords: 2102, errorCodewordsPerBlock: 28 }, Q: { groups: [[48, 24], [14, 25]], totalDataCodewords: 1502, errorCodewordsPerBlock: 30 }, H: { groups: [[42, 15], [32, 16]], totalDataCodewords: 1142, errorCodewordsPerBlock: 30} }, { L: { groups: [[20, 117], [4, 118]], totalDataCodewords: 2812, errorCodewordsPerBlock: 30 }, M: { groups: [[40, 47], [7, 48]], totalDataCodewords: 2216, errorCodewordsPerBlock: 28 }, Q: { groups: [[43, 24], [22, 25]], totalDataCodewords: 1582, errorCodewordsPerBlock: 30 }, H: { groups: [[10, 15], [67, 16]], totalDataCodewords: 1222, errorCodewordsPerBlock: 30} }, { L: { groups: [[19, 118], [6, 119]], totalDataCodewords: 2956, errorCodewordsPerBlock: 30 }, M: { groups: [[18, 47], [31, 48]], totalDataCodewords: 2334, errorCodewordsPerBlock: 28 }, Q: { groups: [[34, 24], [34, 25]], totalDataCodewords: 1666, errorCodewordsPerBlock: 30 }, H: { groups: [[20, 15], [61, 16]], totalDataCodewords: 1276, errorCodewordsPerBlock: 30}}],
        finderPattern = [1, 0, 1, 1, 1],
        alignmentPattern = [1, 0, 1],
        errorCorrectionPatterns = { L: "01", M: "00", Q: "11", H: "10" },
        formatMaskPattern = "101010000010010",
        formatGeneratorPolynomial = "10100110111",
        versionGeneratorPolynomial = "1111100100101",
        paddingCodewords = ["11101100", "00010001"],
        finderPatternValue = 93,
        maskPatternConditions = [
            function (row, column) { return (row + column) % 2 === 0; },
            function (row) { return row % 2 === 0; },
            function (row, column) { return column % 3 === 0; },
            function (row, column) { return (row + column) % 3 === 0; },
            function (row, column) { return (Math.floor(row / 2) + Math.floor(column / 3)) % 2 === 0; },
            function (row, column) { return ((row * column) % 2) + ((row * column) % 3) === 0; },
            function (row, column) { return (((row * column) % 2) + ((row * column) % 3)) % 2 === 0; },
            function (row, column) { return (((row + column) % 2) + ((row * column) % 3)) % 2 === 0; }
        ],
        numberRegex = /^\d+/,
        alphaPattern = "A-Z0-9 $%*+./:-",
        alphaExclusiveSet = "A-Z $%*+./:-",
        alphaRegex = new RegExp("^[" + alphaExclusiveSet + "]+"),
        alphaNumericRegex = new RegExp("^[" + alphaPattern + "]+"),
        byteRegex = new RegExp("^[^" + alphaPattern + "]+"),
        initMinNumericBeforeAlpha = 8,
        initMinNumericBeforeByte = 5,
        initMinAlphaBeforeByte = 8,
        minNumericBeforeAlpha = 17,
        minNumericBeforeByte = 9,
        minAlphaBeforeByte = 16,
        round = Math.round;

    function toDecimal(value) {
        return parseInt(value, 2);
    }

    function toBitsString(value, length) {
        var result = Number(value).toString(2);
        if (result.length < length) {
            result = new Array(length - result.length + 1).join(0) + result;
        }
        return result;
    }

    function splitInto(str, n) {
        var result = [],
                idx = 0;
        while (idx < str.length) {
            result.push(str.substring(idx, idx + n));
            idx += n;
        }
        return result;
    }

    var QRDataMode = kendo.Class.extend({
        getVersionIndex: function (version) {
            if (version < 10) {
                return 0;
            }
            else if (version > 26) {
                return 2;
            }

            return 1;
        },
        getBitsCharacterCount: function (version) {
            var mode = this;
            return mode.bitsInCharacterCount[mode.getVersionIndex(version || 40)];
        },
        getModeCountString: function (length, version) {
            var mode = this;
            return mode.modeIndicator + toBitsString(length, mode.getBitsCharacterCount(version));
        },
        encode: function () { },
        getStringBitsLength: function () { },
        getValue: function () { },
        modeIndicator: "",
        bitsInCharacterCount: []
    });

    var modes = {};
    modes[NUMERIC] = QRDataMode.extend({
        bitsInCharacterCount: [10, 12, 14],
        modeIndicator: "0001",
        getValue: function (character) {
            return parseInt(character, 10);
        },
        encode: function (str, version) {
            var mode = this,
                    parts = splitInto(str, 3),
                    result = mode.getModeCountString(str.length, version);

            for (var i = 0; i < parts.length - 1; i++) {
                result += toBitsString(parts[i], 10);
            }
            return result + toBitsString(parts[i], 1 + 3 * parts[i].length);
        },
        getStringBitsLength: function (inputLength, version) {
            var mod3 = inputLength % 3;
            return 4 + this.getBitsCharacterCount(version) + 10 * Math.floor(inputLength / 3) + 3 * mod3 + (mod3 === 0 ? 0 : 1);
        }
    });

    modes[ALPHA_NUMERIC] = QRDataMode.extend({
        characters: { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15, "G": 16, "H": 17, "I": 18, "J": 19, "K": 20, "L": 21, "M": 22, "N": 23, "O": 24, "P": 25, "Q": 26, "R": 27, "S": 28, "T": 29, "U": 30, "V": 31, "W": 32, "X": 33, "Y": 34, "Z": 35, " ": 36, "$": 37, "%": 38, "*": 39, "+": 40, "-": 41, ".": 42, "/": 43, ":": 44 },
        bitsInCharacterCount: [9, 11, 13],
        modeIndicator: "0010",
        getValue: function (character) {
            return this.characters[character];
        },
        encode: function (str, version) {
            var mode = this,
                    parts = splitInto(str, 2),
                    result = mode.getModeCountString(str.length, version),
                    value;
            for (var i = 0; i < parts.length - 1; i++) {
                value = 45 * mode.getValue(parts[i].charAt(0)) + mode.getValue(parts[i].charAt(1));
                result += toBitsString(value, 11);
            }
            value = parts[i].length == 2 ?
                    45 * mode.getValue(parts[i].charAt(0)) + mode.getValue(parts[i].charAt(1)) :
                    mode.getValue(parts[i].charAt(0));
            return result + toBitsString(value, 1 + 5 * parts[i].length);
        },
        getStringBitsLength: function (inputLength, version) {
            return 4 + this.getBitsCharacterCount(version) + 11 * Math.floor(inputLength / 2) + 6 * (inputLength % 2);
        }
    });

    modes[BYTE] = QRDataMode.extend({
        bitsInCharacterCount: [8, 16, 16],
        modeIndicator: "0100",
        getValue: function (character) {
            var code = character.charCodeAt(0);
            if (code <= 127 || (160 <= code && code <= 255)) {
                return code;
            }
            else {
                throw new Error("Unsupported character: " + character);
            }
        },
        encode: function (str, version) {
            var mode = this,
                    result = mode.getModeCountString(str.length, version);

            for (var i = 0; i < str.length; i++) {
                result += toBitsString(mode.getValue(str.charAt(i)), 8);
            }
            return result;
        },
        getStringBitsLength: function (inputLength, version) {
            return 4 + this.getBitsCharacterCount(version) + 8 * inputLength;
        }
    });

    var modeInstances = {};
    for (var mode in modes) {
        modeInstances[mode] = new modes[mode]();
    }

    var FreeCellVisitor = function (matrix) {
        var that = this,
                row = matrix.length - 1,
                column = matrix.length - 1,
                startColumn = column,
                dir = -1,
                c = 0;
        that.move = function () {
            row += dir * c;
            c ^= 1;
            column = startColumn - c;
        };
        that.getNextCell = function () {
            while (matrix[row][column] !== undefined) {
                that.move();
                if (row < 0 || row >= matrix.length) {
                    dir = -dir;
                    startColumn -= startColumn != 8 ? 2 : 3;
                    column = startColumn;
                    row = dir < 0 ? matrix.length - 1 : 0;
                }
            }
            return { row: row, column: column };
        };
        that.getNextRemainderCell = function () {
            that.move();
            if (matrix[row][column] === undefined) {
                return { row: row, column: column };
            }
        };
    };

    function fillFunctionCell(matrices, bit, x, y) {
        for (var i = 0; i < matrices.length; i++) {
            matrices[i][x][y] = bit;
        }
    }

    function fillDataCell(matrices, bit, x, y) {
        for (var i = 0; i < maskPatternConditions.length; i++) {
            matrices[i][x][y] = maskPatternConditions[i](x, y) ? bit ^ 1 : parseInt(bit, 10);
        }
    }

    var fillData = function (matrices, blocks) {
        var cellVisitor = new FreeCellVisitor(matrices[0]),
                block,
                codewordIdx,
                cell;

        for (var blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
            block = blocks[blockIdx];
            codewordIdx = 0;
            while (block.length > 0) {
                for (var i = 0; i < block.length; i++) {
                    for (var j = 0; j < 8; j++) {
                        cell = cellVisitor.getNextCell();
                        fillDataCell(matrices, block[i][codewordIdx].charAt(j), cell.row, cell.column);
                    }
                }

                codewordIdx++;
                while (block[0] && codewordIdx == block[0].length) {
                    block.splice(0, 1);
                }
            }
        }

        while ((cell = cellVisitor.getNextRemainderCell())) {
            fillDataCell(matrices, 0, cell.row, cell.column);
        }
    };

    var padDataString = function (dataString, totalDataCodewords) {
        var dataBitsCount = totalDataCodewords * 8,
                terminatorIndex = 0,
                paddingCodewordIndex = 0;
        while (dataString.length < dataBitsCount && terminatorIndex < terminator.length) {
            dataString += terminator.charAt(terminatorIndex++);
        }

        if (dataString.length % 8 !== 0) {
            dataString += new Array(9 - dataString.length % 8).join("0");
        }

        while (dataString.length < dataBitsCount) {
            dataString += paddingCodewords[paddingCodewordIndex];
            paddingCodewordIndex ^= 1;
        }
        return dataString;
    };

    function generatePowersOfTwo() {
        var result;
        for (var power = 1; power < 255; power++) {

            result = powersOfTwoResult[power - 1] * 2;
            if (result > 255) {
                result = result ^ 285;
            }

            powersOfTwoResult[power] = result;
            powersOfTwo[result] = power;
        }

        result = (powersOfTwoResult[power - 1] * 2) ^ 285;
        powersOfTwoResult[power] = result;
        powersOfTwoResult[-1] = 0;
    }

    var xorPolynomials = function (x, y) {
        var result = [],
                idx = x.length - 2;
        for (var i = idx; i >= 0; i--) {
            result[i] = x[i] ^ y[i];
        }

        return result;
    };

    var multiplyPolynomials = function (x, y) {
        var result = [];
        for (var i = 0; i < x.length; i++) {
            for (var j = 0; j < y.length; j++) {
                if (result[i + j] === undefined) {
                    result[i + j] = (x[i] + (y[j] >= 0 ? y[j] : 0)) % 255;
                }
                else {
                    result[i + j] = powersOfTwo[powersOfTwoResult[result[i + j]] ^ powersOfTwoResult[(x[i] + y[j]) % 255]];
                }
            }
        }

        return result;
    };

    function generateGeneratorPolynomials() {
        var maxErrorCorrectionCodeWordsCount = 68;
        for (var idx = 2; idx <= maxErrorCorrectionCodeWordsCount; idx++) {
            var firstPolynomial = generatorPolynomials[idx - 1],
                    secondPolynomial = [idx, 0];
            generatorPolynomials[idx] = multiplyPolynomials(firstPolynomial, secondPolynomial);
        }
    }

    //possibly generate on demand
    generatePowersOfTwo();
    generateGeneratorPolynomials();

    function multiplyByConstant(polynomial, power) {
        var result = [],
                idx = polynomial.length - 1;
        do {
            result[idx] = powersOfTwoResult[(polynomial[idx] + power) % 255];
            idx--;
        } while (polynomial[idx] !== undefined);

        return result;
    }

    var generateErrorCodewords = function (data, errorCodewordsCount) {
        var generator = generatorPolynomials[errorCodewordsCount - 1],
                result = new Array(errorCodewordsCount).concat(data),
                generatorPolynomial = new Array(result.length - generator.length).concat(generator),
                steps = data.length,
                errorCodewords = [],
                divisor,
                idx;

        for (idx = 0; idx < steps; idx++) {
            divisor = multiplyByConstant(generatorPolynomial, powersOfTwo[result[result.length - 1]]);
            generatorPolynomial.splice(0, 1);

            result = xorPolynomials(divisor, result);
        }

        for (idx = result.length - 1; idx >= 0; idx--) {
            errorCodewords[errorCodewordsCount - 1 - idx] = toBitsString(result[idx], 8);
        }

        return errorCodewords;
    };

    var getBlocks = function (dataStream, versionCodewordsInformation) {
        var codewordStart = 0,
                dataBlocks = [],
                errorBlocks = [],
                dataBlock,
                versionGroups = versionCodewordsInformation.groups,
                blockCodewordsCount,
                groupBlocksCount,
                messagePolynomial,
                codeword;

        for (var groupIdx = 0; groupIdx < versionGroups.length; groupIdx++) {
            groupBlocksCount = versionGroups[groupIdx][0];
            for (var blockIdx = 0; blockIdx < groupBlocksCount; blockIdx++) {
                blockCodewordsCount = versionGroups[groupIdx][1];
                dataBlock = [];
                messagePolynomial = [];
                for (var codewordIdx = 1; codewordIdx <= blockCodewordsCount; codewordIdx++) {
                    codeword = dataStream.substring(codewordStart, codewordStart + 8);
                    dataBlock.push(codeword);
                    messagePolynomial[blockCodewordsCount - codewordIdx] = toDecimal(codeword);
                    codewordStart += 8;
                }
                dataBlocks.push(dataBlock);
                errorBlocks.push(generateErrorCodewords(messagePolynomial,
                        versionCodewordsInformation.errorCodewordsPerBlock));
            }
        }
        return [dataBlocks, errorBlocks];
    };

    var chooseMode = function (str, minNumericBeforeAlpha, minNumericBeforeByte, minAlphaBeforeByte, previousMode) {
        var numeric = numberRegex.exec(str),
                numericMatch = numeric ? numeric[0] : "",
                alpha = alphaRegex.exec(str),
                alphaMatch = alpha ? alpha[0] : "",
                alphaNumeric = alphaNumericRegex.exec(str),
                alphaNumericMatch = alphaNumeric ? alphaNumeric[0] : "",
                mode,
                modeString;

        if (numericMatch && (numericMatch.length >= minNumericBeforeAlpha ||
                     str.length == numericMatch.length || (numericMatch.length >= minNumericBeforeByte &&
                     !alphaNumericRegex.test(str.charAt(numericMatch.length))))) {
            mode = NUMERIC;
            modeString = numericMatch;
        }
        else if (alphaNumericMatch && (str.length == alphaNumericMatch.length ||
                alphaNumericMatch.length >= minAlphaBeforeByte || previousMode == ALPHA_NUMERIC)) {
            mode = ALPHA_NUMERIC;
            modeString = numericMatch || alphaMatch;
        }
        else {
            mode = BYTE;
            if (alphaNumericMatch) {
                modeString = alphaNumericMatch + byteRegex.exec(str.substring(alphaNumericMatch.length))[0];
            }
            else {
                modeString = byteRegex.exec(str)[0];
            }
        }

        return {
            mode: mode,
            modeString: modeString
        };
    };

    var getModes = function (str) {
        var modes = [],
                previousMode,
                idx = 0;
        modes.push(chooseMode(str, initMinNumericBeforeAlpha, initMinNumericBeforeByte, initMinAlphaBeforeByte, previousMode));
        previousMode = modes[0].mode;
        str = str.substr(modes[0].modeString.length);

        while (str.length > 0) {
            var nextMode = chooseMode(str, minNumericBeforeAlpha, minNumericBeforeByte, minAlphaBeforeByte, previousMode);
            if (nextMode.mode != previousMode) {
                previousMode = nextMode.mode;
                modes.push(nextMode);
                idx++;
            }
            else {
                modes[idx].modeString += nextMode.modeString;
            }
            str = str.substr(nextMode.modeString.length);
        }

        return modes;
    };

    var getDataCodewordsCount = function (modes) {
        var length = 0,
                mode;
        for (var i = 0; i < modes.length; i++) {
            mode = modeInstances[modes[i].mode];
            length += mode.getStringBitsLength(modes[i].modeString.length);
        }

        return Math.ceil(length / 8);
    };

    var getVersion = function (dataCodewordsCount, errorCorrectionLevel) {
        var x = 0,
                y = versionsCodewordsInformation.length - 1,
                version = Math.floor(versionsCodewordsInformation.length / 2);

        do {
            if (dataCodewordsCount < versionsCodewordsInformation[version][errorCorrectionLevel].totalDataCodewords) {
                y = version;
            }
            else {
                x = version;
            }
            version = x + Math.floor((y - x) / 2);

        } while (y - x > 1);

        if (dataCodewordsCount <= versionsCodewordsInformation[x][errorCorrectionLevel].totalDataCodewords) {
            return version + 1;
        }
        return y + 1;
    };

    var getDataString = function (modes, version) {
        var dataString = "",
                mode;
        for (var i = 0; i < modes.length; i++) {
            mode = modeInstances[modes[i].mode];
            dataString += mode.encode(modes[i].modeString, version);
        }

        return dataString;
    };

    //fix case all zeros
    var encodeFormatInformation = function (format) {
        var formatNumber = toDecimal(format),
                encodedString,
                result = "";
        if (formatNumber === 0) {
            return "101010000010010";
        }
        else {
            encodedString = encodeBCH(toDecimal(format), formatGeneratorPolynomial, 15);
        }
        for (var i = 0; i < encodedString.length; i++) {
            result += encodedString.charAt(i) ^ formatMaskPattern.charAt(i);
        }

        return result;
    };

    var encodeBCH = function (value, generatorPolynomial, codeLength) {
        var generatorNumber = toDecimal(generatorPolynomial),
                polynomialLength = generatorPolynomial.length - 1,
                valueNumber = value << polynomialLength,
                length = codeLength - polynomialLength,
                valueString = toBitsString(value, length),
                result = dividePolynomials(valueNumber, generatorNumber);
        result = valueString + toBitsString(result, polynomialLength);
        return result;
    };

    var dividePolynomials = function (numberX, numberY) {
        var yLength = numberY.toString(2).length,
                    xLength = numberX.toString(2).length;
        do {
            numberX ^= numberY << xLength - yLength;
            xLength = numberX.toString(2).length;
        }
        while (xLength >= yLength);

        return numberX;
    };

    function getNumberAt(str, idx) {
        return parseInt(str.charAt(idx), 10);
    }

    var initMatrices = function (version) {
        var matrices = [],
                modules = 17 + 4 * version;
        for (var i = 0; i < maskPatternConditions.length; i++) {
            matrices[i] = new Array(modules);
            for (var j = 0; j < modules; j++) {
                matrices[i][j] = new Array(modules);
            }
        }

        return matrices;
    };

    var addFormatInformation = function (matrices, formatString) {
        var matrix = matrices[0],
                x,
                y,
                idx = 0,
                length = formatString.length;

        for (x = 0, y = 8; x <= 8; x++) {
            if (x !== 6) {
                fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
            }
        }

        for (x = 8, y = 7; y >= 0; y--) {
            if (y !== 6) {
                fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
            }
        }
        idx = 0;
        for (y = matrix.length - 1, x = 8; y >= matrix.length - 8; y--) {
            fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
        }

        fillFunctionCell(matrices, 1, matrix.length - 8, 8);

        for (x = matrix.length - 7, y = 8; x < matrix.length; x++) {
            fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
        }
    };

    var encodeVersionInformation = function (version) {
        return encodeBCH(version, versionGeneratorPolynomial, 18);
    };

    var addVersionInformation = function (matrices, dataString) {
        var matrix = matrices[0],
                modules = matrix.length,
                x1 = 0,
                y1 = modules - 11,
                x2 = modules - 11,
                y2 = 0,
                quotient,
                mod,
                value;

        for (var idx = 0; idx < dataString.length; idx++) {
            quotient = Math.floor(idx / 3);
            mod = idx % 3;
            value = getNumberAt(dataString, dataString.length - idx - 1);
            fillFunctionCell(matrices, value, x1 + quotient, y1 + mod);
            fillFunctionCell(matrices, value, x2 + mod, y2 + quotient);
        }
    };

    var addCentricPattern = function (matrices, pattern, x, y) {
        var size = pattern.length + 2,
                length = pattern.length + 1,
                value;

        for (var i = 0; i < pattern.length; i++) {
            for (var j = i; j < size - i; j++) {
                value = pattern[i];
                fillFunctionCell(matrices, value, x + j, y + i);
                fillFunctionCell(matrices, value, x + i, y + j);
                fillFunctionCell(matrices, value, x + length - j, y + length - i);
                fillFunctionCell(matrices, value, x + length - i, y + length - j);
            }
        }
    };

    var addFinderSeparator = function (matrices, direction, x, y) {
        var nextX = x,
                nextY = y,
                matrix = matrices[0];
        do {
            fillFunctionCell(matrices, 0, nextX, y);
            fillFunctionCell(matrices, 0, x, nextY);
            nextX += direction[0];
            nextY += direction[1];
        }
        while (nextX >= 0 && nextX < matrix.length);
    };

    var addFinderPatterns = function (matrices) {
        var modules = matrices[0].length;
        addCentricPattern(matrices, finderPattern, 0, 0);
        addFinderSeparator(matrices, [-1, -1], 7, 7);
        addCentricPattern(matrices, finderPattern, modules - 7, 0);
        addFinderSeparator(matrices, [1, -1], modules - 8, 7);
        addCentricPattern(matrices, finderPattern, 0, modules - 7);
        addFinderSeparator(matrices, [-1, 1], 7, modules - 8);
    };

    var addAlignmentPatterns = function (matrices, version) {
        if (version < 2) {
            return;
        }

        var matrix = matrices[0],
                modules = matrix.length,
                pointsCount = Math.floor(version / 7),
                points = [6],
                startDistance,
                distance,
                idx = 0;

        if ((startDistance = irregularAlignmentPatternsStartDistance[version])) {
            distance = (modules - 13 - startDistance) / pointsCount;
        }
        else {
            startDistance = distance = (modules - 13) / (pointsCount + 1);
        }
        points.push(points[idx++] + startDistance);
        while ((points[idx] + distance) < modules) {
            points.push(points[idx++] + distance);
        }
        for (var i = 0; i < points.length; i++) {
            for (var j = 0; j < points.length; j++) {
                if (matrix[points[i]][points[j]] === undefined) {
                    addCentricPattern(matrices, alignmentPattern, points[i] - 2, points[j] - 2);
                }
            }
        }
    };

    var addTimingFunctions = function (matrices) {
        var row = 6,
                column = 6,
                value = 1,
                modules = matrices[0].length;
        for (var i = 8; i < modules - 8; i++) {
            fillFunctionCell(matrices, value, row, i);
            fillFunctionCell(matrices, value, i, column);
            value ^= 1;
        }
    };

    var scoreMaskMatrixes = function (matrices) {
        var scores = [],
                previousBits = [],
                darkModules = [],
                patterns = [],
                adjacentSameBits = [],
                matrix,
                i,
                row = 0,
                column = 1,
                modules = matrices[0].length;


        for (i = 0; i < matrices.length; i++) {
            scores[i] = 0;
            darkModules[i] = 0;
            adjacentSameBits[i] = [0, 0];
            patterns[i] = [0, 0];
            previousBits[i] = [];
        }
        for (i = 0; i < modules; i++) {
            for (var j = 0; j < modules; j++) {
                for (var k = 0; k < matrices.length; k++) {
                    matrix = matrices[k];
                    darkModules[k] += parseInt(matrix[i][j], 10);
                    if (previousBits[k][row] === matrix[i][j] && i + 1 < modules && j - 1 >= 0 &&
                            matrix[i + 1][j] == previousBits[k][row] && matrix[i + 1][j - 1] == previousBits[k][row]) {
                        scores[k] += 3;
                    }
                    scoreFinderPatternOccurance(k, patterns, scores, row, matrix[i][j]);
                    scoreFinderPatternOccurance(k, patterns, scores, column, matrix[j][i]);
                    scoreAdjacentSameBits(k, scores, previousBits, matrix[i][j], adjacentSameBits, row);
                    scoreAdjacentSameBits(k, scores, previousBits, matrix[j][i], adjacentSameBits, column);
                }
            }
        }
        var total = modules * modules,
                minIdx,
                min = Number.MAX_VALUE;

        for (i = 0; i < scores.length; i++) {
            scores[i] += calculateDarkModulesRatioScore(darkModules[i], total);
            if (scores[i] < min) {
                min = scores[i];
                minIdx = i;
            }
        }

        return minIdx;
    };

    function scoreFinderPatternOccurance(idx, patterns, scores, rowColumn, bit) {
        patterns[idx][rowColumn] = ((patterns[idx][rowColumn] << 1) ^ bit) % 128;
        if (patterns[idx][rowColumn] == finderPatternValue) {
            scores[idx] += 40;
        }
    }

    function scoreAdjacentSameBits(idx, scores, previousBits, bit, adjacentBits, rowColumn) {
        if (previousBits[idx][rowColumn] == bit) {
            adjacentBits[idx][rowColumn]++;
        }
        else {
            previousBits[idx][rowColumn] = bit;
            if (adjacentBits[idx][rowColumn] >= 5) {
                scores[idx] += 3 + adjacentBits[idx][rowColumn] - 5;
            }
            adjacentBits[idx][rowColumn] = 1;
        }
    }

    function calculateDarkModulesRatioScore(darkModules, total) {
        var percent = Math.floor((darkModules / total) * 100),
                mod5 = percent % 5,
                previous = Math.abs(percent - mod5 - 50),
                next = Math.abs(percent + 5 - mod5 - 50),
                score = 10 * Math.min(previous / 5, next / 5);
        return score;
    }

    var EncodingResult = function (dataString, version) {
        this.dataString = dataString;
        this.version = version;
    };

    var IsoEncoder = function () {
        this.getEncodingResult = function (inputString, errorCorrectionLevel) {
            var modes = getModes(inputString),
                dataCodewordsCount = getDataCodewordsCount(modes),
                version = getVersion(dataCodewordsCount, errorCorrectionLevel),
                dataString = getDataString(modes, version);

            return new EncodingResult(dataString, version);
        };
    };

    var UTF8Encoder = function () {
        this.mode = modeInstances[this.encodingMode];
    };

    UTF8Encoder.fn = UTF8Encoder.prototype = {
        encodingMode: BYTE,
        utfBOM: "111011111011101110111111",
        initialModeCountStringLength: 20,
        getEncodingResult: function (inputString, errorCorrectionLevel) {
            var that = this,
                    data = that.encode(inputString),
                    dataCodewordsCount = that.getDataCodewordsCount(data),
                    version = getVersion(dataCodewordsCount, errorCorrectionLevel),
                    dataString = that.mode.getModeCountString(data.length / 8, version) + data;

            return new EncodingResult(dataString, version);
        },
        getDataCodewordsCount: function (data) {
            var that = this,
                    dataLength = data.length,
                    dataCodewordsCount = Math.ceil((that.initialModeCountStringLength + dataLength) / 8);

            return dataCodewordsCount;
        },
        encode: function (str) {
            var that = this,
                    result = that.utfBOM;
            for (var i = 0; i < str.length; i++) {
                result += that.encodeCharacter(str.charCodeAt(i));
            }
            return result;
        },
        encodeCharacter: function (code) {
            var bytesCount = this.getBytesCount(code),
                    bc = bytesCount - 1,
                    result = "";

            if (bytesCount == 1) {
                result = toBitsString(code, 8);
            }
            else {
                var significantOnes = 8 - bytesCount;

                for (var i = 0; i < bc; i++) {
                    result = toBitsString(code >> (i * 6) & 63 | 128, 8) + result;
                }

                result = ((code >> bc * 6) | ((255 >> significantOnes) << significantOnes)).toString(2) + result;
            }
            return result;
        },
        getBytesCount: function (code) {
            var ranges = this.ranges;
            for (var i = 0; i < ranges.length; i++) {
                if (code < ranges[i]) {
                    return i + 1;
                }
            }
        },
        ranges: [128, 2048, 65536, 2097152, 67108864]
    };

    var QRCodeDataEncoder = function (encoding) {
        if (encoding && encoding.toLowerCase().indexOf("utf_8") >= 0) {
            return new UTF8Encoder();
        }
        else {
            return new IsoEncoder();
        }
    };

    var encodeData = function (inputString, errorCorrectionLevel, encoding) {
        var encoder = new QRCodeDataEncoder(encoding),
                encodingResult = encoder.getEncodingResult(inputString, errorCorrectionLevel),
                version = encodingResult.version,
                versionInformation = versionsCodewordsInformation[version - 1][errorCorrectionLevel],
                dataString = padDataString(encodingResult.dataString, versionInformation.totalDataCodewords),
                blocks = getBlocks(dataString, versionInformation),
                matrices = initMatrices(version);

        addFinderPatterns(matrices);
        addAlignmentPatterns(matrices, version);
        addTimingFunctions(matrices);

        if (version >= 7) {
            addVersionInformation(matrices, toBitsString(0, 18));
        }

        addFormatInformation(matrices, toBitsString(0, 15));
        fillData(matrices, blocks);

        var minIdx = scoreMaskMatrixes(matrices),
                optimalMatrix = matrices[minIdx];

        if (version >= 7) {
            addVersionInformation([optimalMatrix], encodeVersionInformation(version));
        }

        var formatString = errorCorrectionPatterns[errorCorrectionLevel] + toBitsString(minIdx, 3);
        addFormatInformation([optimalMatrix], encodeFormatInformation(formatString));

        return optimalMatrix;
    };

    var QRCodeDefaults = {
        DEFAULT_SIZE: 200,
        QUIET_ZONE_LENGTH: 4,
        DEFAULT_ERROR_CORRECTION_LEVEL: "L",
        DEFAULT_BACKGROUND: "#fff",
        DEFAULT_DARK_MODULE_COLOR: "#000",
        MIN_BASE_UNIT_SIZE: 1
    };

    var QRCode = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            that.element = $(element);
            that.wrapper = that.element;
            that.element.addClass("k-qrcode");
            that.surfaceWrap = $("<div />").css("position", "relative").appendTo(this.element);
            that.surface = draw.Surface.create(that.surfaceWrap, {
                type: that.options.renderAs
            });
            that.setOptions(options);
        },

        redraw: function () {
            var size = this._getSize();

            this.surfaceWrap.css({
                width: size,
                height: size
            });
            this.surface.clear();

            this.createVisual();
            this.surface.draw(this.visual);
        },

        getSize: function () {
            return kendo.dimensions(this.element);
        },

        _resize: function () {
            this.redraw();
        },

        createVisual: function () {
            this.visual = this._render();
        },

        exportVisual: function () {
            return this._render();
        },

        _render: function () {
            var that = this,
                    value = that._value,
                    baseUnit,
                    border = that.options.border || {},
                    padding = that.options.padding || 0,
                    borderWidth = border.width || 0,
                    quietZoneSize,
                    matrix,
                    size,
                    dataSize,
                    contentSize;

            border.width = borderWidth;

            var visual = new draw.Group();

            if (value) {
                matrix = encodeData(value, that.options.errorCorrection, that.options.encoding);
                size = that._getSize();
                contentSize = size - 2 * (borderWidth + padding);
                baseUnit = that._calculateBaseUnit(contentSize, matrix.length);
                dataSize = matrix.length * baseUnit;
                quietZoneSize = borderWidth + padding + (contentSize - dataSize) / 2;

                visual.append(that._renderBackground(size, border));
                visual.append(that._renderMatrix(matrix, baseUnit, quietZoneSize));
            }

            return visual;
        },

        _getSize: function () {
            var that = this,
                    size;

            if (that.options.size) {
                size = parseInt(that.options.size, 10);
            } else {
                var element = that.element,
                        min = Math.min(element.width(), element.height());

                if (min > 0) {
                    size = min;
                } else {
                    size = QRCodeDefaults.DEFAULT_SIZE;
                }
            }

            return size;
        },

        _calculateBaseUnit: function (size, matrixSize) {
            var baseUnit = Math.floor(size / matrixSize);

            if (baseUnit < QRCodeDefaults.MIN_BASE_UNIT_SIZE) {
                throw new Error("Insufficient size.");
            }

            if (baseUnit * matrixSize >= size &&
                    baseUnit - 1 >= QRCodeDefaults.MIN_BASE_UNIT_SIZE) {
                baseUnit--;
            }

            return baseUnit;
        },

        _renderMatrix: function (matrix, baseUnit, quietZoneSize) {
            var path = new draw.MultiPath({
                fill: {
                    color: this.options.color
                },
                stroke: null
            });

            for (var row = 0; row < matrix.length; row++) {
                var y = quietZoneSize + row * baseUnit;
                var column = 0;

                while (column < matrix.length) {
                    while (matrix[row][column] === 0 && column < matrix.length) {
                        column++;
                    }

                    if (column < matrix.length) {
                        var x = column;
                        while (matrix[row][column] == 1) {
                            column++;
                        }

                        var x1 = round(quietZoneSize + x * baseUnit);
                        var y1 = round(y);
                        var x2 = round(quietZoneSize + column * baseUnit);
                        var y2 = round(y + baseUnit);

                        path.moveTo(x1, y1)
                                .lineTo(x1, y2)
                                .lineTo(x2, y2)
                                .lineTo(x2, y1)
                                .close();
                    }
                }
            }

            return path;
        },

        _renderBackground: function (size, border) {
            var box = Box2D(0, 0, size, size).unpad(border.width / 2);
            return draw.Path.fromRect(box.toRect(), {
                fill: {
                    color: this.options.background
                },
                stroke: {
                    color: border.color,
                    width: border.width
                }
            });
        },

        setOptions: function (options) {
            var that = this;
            options = options || {};
            that.options = extend(that.options, options);
            if (options.value !== undefined) {
                that._value = that.options.value + "";
            }
            that.redraw();
        },
        value: function (value) {
            var that = this;
            if (value === undefined) {
                return that._value;
            }
            that._value = value + "";
            that.redraw();
        },
        options: {
            name: "QRCode",
            renderAs: "svg",
            encoding: "ISO_8859_1",
            value: "",
            errorCorrection: QRCodeDefaults.DEFAULT_ERROR_CORRECTION_LEVEL,
            background: QRCodeDefaults.DEFAULT_BACKGROUND,
            color: QRCodeDefaults.DEFAULT_DARK_MODULE_COLOR,
            size: "",
            padding: 0,
            border: {
                color: "",
                width: 0
            }
        }
    });

    dataviz.ExportMixin.extend(QRCode.fn);
    dataviz.ui.plugin(QRCode);

    kendo.deepExtend(dataviz, {
        QRCode: QRCode,
        QRCodeDefaults: QRCodeDefaults,
        QRCodeFunctions: {
            FreeCellVisitor: FreeCellVisitor,
            fillData: fillData,
            padDataString: padDataString,
            generateErrorCodewords: generateErrorCodewords,
            xorPolynomials: xorPolynomials,
            getBlocks: getBlocks,
            multiplyPolynomials: multiplyPolynomials,
            chooseMode: chooseMode,
            getModes: getModes,
            getDataCodewordsCount: getDataCodewordsCount,
            getVersion: getVersion,
            getDataString: getDataString,
            encodeFormatInformation: encodeFormatInformation,
            encodeBCH: encodeBCH,
            dividePolynomials: dividePolynomials,
            initMatrices: initMatrices,
            addFormatInformation: addFormatInformation,
            encodeVersionInformation: encodeVersionInformation,
            addVersionInformation: addVersionInformation,
            addCentricPattern: addCentricPattern,
            addFinderSeparator: addFinderSeparator,
            addFinderPatterns: addFinderPatterns,
            addAlignmentPatterns: addAlignmentPatterns,
            addTimingFunctions: addTimingFunctions,
            scoreMaskMatrixes: scoreMaskMatrixes,
            encodeData: encodeData,
            UTF8Encoder: UTF8Encoder
        },
        QRCodeFields: {
            modes: modeInstances,
            powersOfTwo: powersOfTwo,
            powersOfTwoResult: powersOfTwoResult,
            generatorPolynomials: generatorPolynomials
        }
    });

})(window.kendo.jQuery);





(function ($, undefined) {
    // Imports ================================================================
    var kendo = window.kendo,
        Class = kendo.Class,
        Observable = kendo.Observable,
        deepExtend = kendo.deepExtend,
        math = Math,
        proxy = $.proxy,

        util = kendo.util,
        last = util.last,
        renderTemplate = util.renderTemplate,

        dataviz = kendo.dataviz,
        defined = util.defined,
        filterSeriesByType = dataviz.filterSeriesByType,
        template = kendo.template,
        Chart = dataviz.ui.Chart,
        Selection = dataviz.Selection,
        addDuration = dataviz.addDuration,
        limitValue = util.limitValue,
        lteDateIndex = dataviz.lteDateIndex,
        toDate = dataviz.toDate,
        toTime = dataviz.toTime;

    // Constants =============================================================
    var AUTO_CATEGORY_WIDTH = 28,
        CHANGE = "change",
        CSS_PREFIX = "k-",
        DRAG = "drag",
        DRAG_END = "dragEnd",
        NAVIGATOR_PANE = "_navigator",
        NAVIGATOR_AXIS = NAVIGATOR_PANE,
        EQUALLY_SPACED_SERIES = dataviz.EQUALLY_SPACED_SERIES,
        ZOOM_ACCELERATION = 3,
        ZOOM = "zoom",
        ZOOM_END = "zoomEnd";

    // Stock chart ===========================================================
    var StockChart = Chart.extend({
        init: function (element, userOptions) {
            $(element).addClass(CSS_PREFIX + "chart");
            Chart.fn.init.call(this, element, userOptions);
        },

        _applyDefaults: function (options, themeOptions) {
            var chart = this,
                width = chart.element.width() || dataviz.DEFAULT_WIDTH;

            var stockDefaults = {
                seriesDefaults: {
                    categoryField: options.dateField
                },
                axisDefaults: {
                    categoryAxis: {
                        name: "default",
                        majorGridLines: {
                            visible: false
                        },
                        labels: {
                            step: 2
                        },
                        majorTicks: {
                            visible: false
                        },
                        maxDateGroups: math.floor(width / AUTO_CATEGORY_WIDTH)
                    }
                }
            };

            if (themeOptions) {
                themeOptions = deepExtend({}, themeOptions, stockDefaults);
            }

            if (!chart._navigator) {
                Navigator.setup(options, themeOptions);
            }

            Chart.fn._applyDefaults.call(chart, options, themeOptions);
        },

        _initDataSource: function (userOptions) {
            var options = userOptions || {},
                dataSource = options.dataSource,
                hasServerFiltering = dataSource && dataSource.serverFiltering,
                mainAxis = [].concat(options.categoryAxis)[0],
                naviOptions = options.navigator || {},
                select = naviOptions.select,
                hasSelect = select && select.from && select.to,
                filter,
                dummyAxis;

            if (hasServerFiltering && hasSelect) {
                filter = [].concat(dataSource.filter || []);

                dummyAxis = new dataviz.DateCategoryAxis(deepExtend({
                    baseUnit: "fit"
                }, mainAxis, {
                    categories: [select.from, select.to]
                }));

                dataSource.filter =
                    Navigator.buildFilter(dummyAxis.range().min, select.to)
                    .concat(filter);
            }

            Chart.fn._initDataSource.call(this, userOptions);
        },

        options: {
            name: "StockChart",
            dateField: "date",
            axisDefaults: {
                categoryAxis: {
                    type: "date",
                    baseUnit: "fit",
                    justified: true
                },
                valueAxis: {
                    narrowRange: true,
                    labels: {
                        format: "C"
                    }
                }
            },
            navigator: {
                select: {},
                seriesDefaults: {
                    markers: {
                        visible: false
                    },
                    tooltip: {
                        visible: true,
                        template: "#= kendo.toString(category, 'd') #"
                    },
                    line: {
                        width: 2
                    }
                },
                hint: {},
                visible: true
            },
            tooltip: {
                visible: true
            },
            legend: {
                visible: false
            }
        },

        _resize: function () {
            var t = this.options.transitions;

            this.options.transitions = false;
            this._fullRedraw();
            this.options.transitions = t;
        },

        _redraw: function () {
            var chart = this,
                navigator = chart._navigator;

            if (navigator && navigator.dataSource) {
                navigator.redrawSlaves();
            } else {
                chart._fullRedraw();
            }
        },

        _fullRedraw: function () {
            var chart = this,
                navigator = chart._navigator;

            if (!navigator) {
                navigator = chart._navigator = new Navigator(chart);
            }

            navigator._setRange();
            Chart.fn._redraw.call(chart);
            navigator._initSelection();
        },

        _onDataChanged: function () {
            var chart = this;

            Chart.fn._onDataChanged.call(chart);
            chart._dataBound = true;
        },

        _bindCategoryAxis: function (axis, data, axisIx) {
            var chart = this,
                categoryAxes = chart.options.categoryAxis,
                axesLength = categoryAxes.length,
                currentAxis;

            Chart.fn._bindCategoryAxis.apply(this, arguments);

            if (axis.name === NAVIGATOR_AXIS) {
                while (axisIx < axesLength) {
                    currentAxis = categoryAxes[axisIx++];
                    if (currentAxis.pane == NAVIGATOR_PANE) {
                        currentAxis.categories = axis.categories;
                    }
                }
            }
        },

        _trackSharedTooltip: function (coords) {
            var chart = this,
                plotArea = chart._plotArea,
                pane = plotArea.paneByPoint(coords);

            if (pane && pane.options.name === NAVIGATOR_PANE) {
                chart._unsetActivePoint();
            } else {
                Chart.fn._trackSharedTooltip.call(chart, coords);
            }
        },

        destroy: function () {
            var chart = this;

            chart._navigator.destroy();

            Chart.fn.destroy.call(chart);
        }
    });

    var Navigator = Observable.extend({
        init: function (chart) {
            var navi = this;

            navi.chart = chart;
            navi.options = deepExtend({}, navi.options, chart.options.navigator);

            navi._initDataSource();

            if (!defined(navi.options.hint.visible)) {
                navi.options.hint.visible = navi.options.visible;
            }

            chart.bind(DRAG, proxy(navi._drag, navi));
            chart.bind(DRAG_END, proxy(navi._dragEnd, navi));
            chart.bind(ZOOM, proxy(navi._zoom, navi));
            chart.bind(ZOOM_END, proxy(navi._zoomEnd, navi));
        },

        options: {},

        _initDataSource: function () {
            var navi = this,
                options = navi.options,
                autoBind = options.autoBind,
                dsOptions = options.dataSource;

            if (!defined(autoBind)) {
                autoBind = navi.chart.options.autoBind;
            }

            navi._dataChangedHandler = proxy(navi._onDataChanged, navi);

            if (dsOptions) {
                navi.dataSource = kendo.data.DataSource
                    .create(dsOptions)
                    .bind(CHANGE, navi._dataChangedHandler);

                if (autoBind) {
                    navi.dataSource.fetch();
                }
            }
        },

        _onDataChanged: function () {
            var navi = this,
                chart = navi.chart,
                series = chart.options.series,
                seriesIx,
                seriesLength = series.length,
                categoryAxes = chart.options.categoryAxis,
                axisIx,
                axesLength = categoryAxes.length,
                data = navi.dataSource.view(),
                currentSeries,
                currentAxis,
                naviCategories;

            for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
                currentSeries = series[seriesIx];

                if (currentSeries.axis == NAVIGATOR_AXIS && chart._isBindable(currentSeries)) {
                    currentSeries.data = data;
                }
            }

            for (axisIx = 0; axisIx < axesLength; axisIx++) {
                currentAxis = categoryAxes[axisIx];

                if (currentAxis.pane == NAVIGATOR_PANE) {
                    if (currentAxis.name == NAVIGATOR_AXIS) {
                        chart._bindCategoryAxis(currentAxis, data, axisIx);
                        naviCategories = currentAxis.categories;
                    } else {
                        currentAxis.categories = naviCategories;
                    }
                }
            }

            if (chart._model) {
                navi.redraw();
                navi.filterAxes();

                if (!chart.options.dataSource || (chart.options.dataSource && chart._dataBound)) {
                    navi.redrawSlaves();
                }
            }
        },

        destroy: function () {
            var navi = this,
                dataSource = navi.dataSource;

            if (dataSource) {
                dataSource.unbind(CHANGE, navi._dataChangeHandler);
            }

            if (navi.selection) {
                navi.selection.destroy();
            }
        },

        redraw: function () {
            this._redrawSelf();
            this._initSelection();
        },

        _initSelection: function () {
            var navi = this,
                chart = navi.chart,
                options = navi.options,
                axis = navi.mainAxis(),
                axisClone = clone(axis),
                range = axis.range(),
                min = range.min,
                max = range.max,
                groups = axis.options.categories,
                select = navi.options.select,
                selection = navi.selection,
                from = toDate(select.from),
                to = toDate(select.to);

            if (groups.length === 0) {
                return;
            }

            if (selection) {
                selection.destroy();
                selection.wrapper.remove();
            }

            // "Freeze" the selection axis position until the next redraw
            axisClone.box = axis.box;

            selection = navi.selection = new Selection(chart, axisClone, {
                min: min,
                max: max,
                from: from,
                to: to,
                selectStart: $.proxy(navi._selectStart, navi),
                select: $.proxy(navi._select, navi),
                selectEnd: $.proxy(navi._selectEnd, navi),
                mousewheel: {
                    zoom: "left"
                }
            });

            if (options.hint.visible) {
                navi.hint = new NavigatorHint(chart.element, {
                    min: min,
                    max: max,
                    template: options.hint.template,
                    format: options.hint.format
                });
            }
        },

        _setRange: function () {
            var plotArea = this.chart._createPlotArea(true);
            var axis = plotArea.namedCategoryAxes[NAVIGATOR_AXIS];
            var axisOpt = axis.options;

            var range = axis.range();
            var min = range.min;
            var max = addDuration(range.max, axisOpt.baseUnitStep, axisOpt.baseUnit);

            var select = this.options.select || {};
            var from = toDate(select.from) || min;
            if (from < min) {
                from = min;
            }

            var to = toDate(select.to) || max;
            if (to > max) {
                to = max;
            }

            this.options.select = {
                from: from,
                to: to
            };

            this.filterAxes();
        },

        _redrawSelf: function (silent) {
            var plotArea = this.chart._plotArea;

            if (plotArea) {
                plotArea.redraw(last(plotArea.panes), silent);
            }
        },

        redrawSlaves: function () {
            var navi = this,
                chart = navi.chart,
                plotArea = chart._plotArea,
                slavePanes = plotArea.panes.slice(0, -1);

            // Update the original series before partial refresh.
            plotArea.srcSeries = chart.options.series;

            plotArea.redraw(slavePanes);
        },

        _drag: function (e) {
            var navi = this,
                chart = navi.chart,
                coords = chart._eventCoordinates(e.originalEvent),
                navigatorAxis = navi.mainAxis(),
                naviRange = navigatorAxis.range(),
                inNavigator = navigatorAxis.pane.box.containsPoint(coords),
                axis = chart._plotArea.categoryAxis,
                range = e.axisRanges[axis.options.name],
                select = navi.options.select,
                selection = navi.selection,
                duration,
                from,
                to;

            if (!range || inNavigator || !selection) {
                return;
            }

            if (select.from && select.to) {
                duration = toTime(select.to) - toTime(select.from);
            } else {
                duration = toTime(selection.options.to) - toTime(selection.options.from);
            }

            from = toDate(limitValue(
                toTime(range.min),
                naviRange.min, toTime(naviRange.max) - duration
            ));

            to = toDate(limitValue(
                toTime(from) + duration,
                toTime(naviRange.min) + duration, naviRange.max
            ));

            navi.options.select = { from: from, to: to };

            if (navi._liveDrag()) {
                navi.filterAxes();
                navi.redrawSlaves();
            }

            selection.set(
                from,
                to
            );

            navi.showHint(from, to);
        },

        _dragEnd: function () {
            var navi = this;

            navi.filterAxes();
            navi.filterDataSource();
            navi.redrawSlaves();

            if (navi.hint) {
                navi.hint.hide();
            }
        },

        _liveDrag: function () {
            var support = kendo.support,
                isTouch = support.touch,
                browser = support.browser,
                isFirefox = browser.mozilla,
                isOldIE = browser.msie && browser.version < 9;

            return !isTouch && !isFirefox && !isOldIE;
        },

        readSelection: function () {
            var navi = this,
                selection = navi.selection,
                src = selection.options,
                dst = navi.options.select;

            dst.from = src.from;
            dst.to = src.to;
        },

        filterAxes: function () {
            var navi = this,
                select = navi.options.select || {},
                chart = navi.chart,
                allAxes = chart.options.categoryAxis,
                from = select.from,
                to = select.to,
                i,
                axis;

            for (i = 0; i < allAxes.length; i++) {
                axis = allAxes[i];
                if (axis.pane !== NAVIGATOR_PANE) {
                    axis.min = toDate(from);
                    axis.max = toDate(to);
                }
            }
        },

        filterDataSource: function () {
            var navi = this,
                select = navi.options.select || {},
                chart = navi.chart,
                chartDataSource = chart.dataSource,
                hasServerFiltering = chartDataSource && chartDataSource.options.serverFiltering,
                axisOptions;

            if (navi.dataSource && hasServerFiltering) {
                axisOptions = new dataviz.DateCategoryAxis(deepExtend({
                    baseUnit: "fit"
                }, chart.options.categoryAxis[0], {
                    categories: [select.from, select.to]
                })).options;

                chartDataSource.filter(
                    Navigator.buildFilter(
                        addDuration(axisOptions.min, -axisOptions.baseUnitStep, axisOptions.baseUnit),
                        addDuration(axisOptions.max, axisOptions.baseUnitStep, axisOptions.baseUnit)
                    )
                );
            }
        },

        _zoom: function (e) {
            var navi = this,
                chart = navi.chart,
                delta = e.delta,
                axis = chart._plotArea.categoryAxis,
                select = navi.options.select,
                selection = navi.selection,
                categories = navi.mainAxis().options.categories,
                fromIx,
                toIx;

            if (!selection) {
                return;
            }

            fromIx = lteDateIndex(selection.options.from, categories);
            toIx = lteDateIndex(selection.options.to, categories);

            e.originalEvent.preventDefault();

            if (math.abs(delta) > 1) {
                delta *= ZOOM_ACCELERATION;
            }

            if (toIx - fromIx > 1) {
                selection.expand(delta);
                navi.readSelection();
            } else {
                axis.options.min = select.from;
                select.from = axis.scaleRange(-e.delta).min;
            }

            if (!kendo.support.touch) {
                navi.filterAxes();
                navi.redrawSlaves();
            }

            selection.set(select.from, select.to);

            navi.showHint(navi.options.select.from, navi.options.select.to);
        },

        _zoomEnd: function (e) {
            this._dragEnd(e);
        },

        showHint: function (from, to) {
            var navi = this,
                chart = navi.chart,
                plotArea = chart._plotArea;

            if (navi.hint) {
                navi.hint.show(
                    from,
                    to,
                    plotArea.backgroundBox()
                );
            }
        },

        _selectStart: function (e) {
            var chart = this.chart;
            chart._selectStart.call(chart, e);
        },

        _select: function (e) {
            var navi = this,
                chart = navi.chart;

            navi.showHint(e.from, e.to);

            chart._select.call(chart, e);
        },

        _selectEnd: function (e) {
            var navi = this,
                chart = navi.chart;

            if (navi.hint) {
                navi.hint.hide();
            }

            navi.readSelection();
            navi.filterAxes();
            navi.filterDataSource();
            navi.redrawSlaves();

            chart._selectEnd.call(chart, e);
        },

        mainAxis: function () {
            var plotArea = this.chart._plotArea;

            if (plotArea) {
                return plotArea.namedCategoryAxes[NAVIGATOR_AXIS];
            }
        }
    });

    Navigator.setup = function (options, themeOptions) {
        options = options || {};
        themeOptions = themeOptions || {};

        var naviOptions = deepExtend({}, themeOptions.navigator, options.navigator),
            panes = options.panes = [].concat(options.panes),
            paneOptions = deepExtend({}, naviOptions.pane, { name: NAVIGATOR_PANE });

        if (!naviOptions.visible) {
            paneOptions.visible = false;
            paneOptions.height = 0.1;
        }

        panes.push(paneOptions);

        Navigator.attachAxes(options, naviOptions);
        Navigator.attachSeries(options, naviOptions, themeOptions);
    };

    Navigator.attachAxes = function (options, naviOptions) {
        var categoryAxes,
            valueAxes,
            series = naviOptions.series || [];

        categoryAxes = options.categoryAxis = [].concat(options.categoryAxis);
        valueAxes = options.valueAxis = [].concat(options.valueAxis);

        var equallySpacedSeries = filterSeriesByType(series, EQUALLY_SPACED_SERIES);
        var justifyAxis = equallySpacedSeries.length === 0;

        var base = deepExtend({
            type: "date",
            pane: NAVIGATOR_PANE,
            roundToBaseUnit: !justifyAxis,
            justified: justifyAxis,
            _collapse: false,
            tooltip: { visible: false },
            labels: { step: 1 },
            autoBind: !naviOptions.dataSource,
            autoBaseUnitSteps: {
                minutes: [1],
                hours: [1, 2],
                days: [1, 2],
                weeks: [],
                months: [1],
                years: [1]
            },
            _overlap: false
        });
        var user = naviOptions.categoryAxis;

        categoryAxes.push(
            deepExtend({}, base, {
                maxDateGroups: 200
            }, user, {
                name: NAVIGATOR_AXIS,
                baseUnit: "fit",
                baseUnitStep: "auto",
                labels: { visible: false },
                majorTicks: { visible: false }
            }), deepExtend({}, base, user, {
                name: NAVIGATOR_AXIS + "_labels",
                maxDateGroups: 20,
                baseUnitStep: "auto",
                autoBaseUnitSteps: {
                    minutes: []
                },
                majorTicks: { visible: true }
            }), deepExtend({}, base, user, {
                name: NAVIGATOR_AXIS + "_ticks",
                maxDateGroups: 200,
                majorTicks: {
                    visible: true,
                    width: 0.5
                },
                labels: { visible: false, mirror: true }
            })
        );

        valueAxes.push(deepExtend({
            name: NAVIGATOR_AXIS,
            pane: NAVIGATOR_PANE,
            majorGridLines: {
                visible: false
            },
            visible: false
        }, naviOptions.valueAxis));
    };

    Navigator.attachSeries = function (options, naviOptions, themeOptions) {
        var series = options.series = options.series || [],
            navigatorSeries = [].concat(naviOptions.series || []),
            seriesColors = themeOptions.seriesColors,
            defaults = naviOptions.seriesDefaults,
            i;

        for (i = 0; i < navigatorSeries.length; i++) {
            series.push(
                deepExtend({
                    color: seriesColors[i % seriesColors.length],
                    categoryField: naviOptions.dateField,
                    visibleInLegend: false,
                    tooltip: {
                        visible: false
                    }
                }, defaults, navigatorSeries[i], {
                    axis: NAVIGATOR_AXIS,
                    categoryAxis: NAVIGATOR_AXIS,
                    autoBind: !naviOptions.dataSource
                })
            );
        }
    };

    Navigator.buildFilter = function (from, to) {
        return [{
            field: "Date", operator: "gte", value: toDate(from)
        }, {
            field: "Date", operator: "lt", value: toDate(to)
        }];
    };

    var NavigatorHint = Class.extend({
        init: function (container, options) {
            var hint = this;

            hint.options = deepExtend({}, hint.options, options);

            hint.container = container;
            hint.chartPadding = {
                top: parseInt(container.css("paddingTop"), 10),
                left: parseInt(container.css("paddingLeft"), 10)
            };

            hint.template = hint.template;
            if (!hint.template) {
                hint.template = hint.template = renderTemplate(
                    "<div class='" + CSS_PREFIX + "navigator-hint' " +
                    "style='display: none; position: absolute; top: 1px; left: 1px;'>" +
                        "<div class='" + CSS_PREFIX + "tooltip " + CSS_PREFIX + "chart-tooltip'>&nbsp;</div>" +
                        "<div class='" + CSS_PREFIX + "scroll' />" +
                    "</div>"
                );
            }

            hint.element = $(hint.template()).appendTo(container);
        },

        options: {
            format: "{0:d} - {1:d}",
            hideDelay: 500
        },

        show: function (from, to, bbox) {
            var hint = this,
                middle = toDate(toTime(from) + toTime(to - from) / 2),
                options = hint.options,
                text = kendo.format(hint.options.format, from, to),
                tooltip = hint.element.find("." + CSS_PREFIX + "tooltip"),
                scroll = hint.element.find("." + CSS_PREFIX + "scroll"),
                scrollWidth = bbox.width() * 0.4,
                minPos = bbox.center().x - scrollWidth,
                maxPos = bbox.center().x,
                posRange = maxPos - minPos,
                range = options.max - options.min,
                scale = posRange / range,
                offset = middle - options.min,
                hintTemplate;

            if (hint._hideTimeout) {
                clearTimeout(hint._hideTimeout);
            }

            if (!hint._visible) {
                hint.element
                    .stop(false, true)
                    .css("visibility", "hidden")
                    .show();
                hint._visible = true;
            }

            if (options.template) {
                hintTemplate = template(options.template);
                text = hintTemplate({
                    from: from,
                    to: to
                });
            }

            tooltip
                .html(text)
                .css({
                    left: bbox.center().x - tooltip.outerWidth() / 2,
                    top: bbox.y1
                });

            scroll
                .css({
                    width: scrollWidth,
                    left: minPos + offset * scale,
                    top: bbox.y1 +
                         parseInt(tooltip.css("margin-top"), 10) +
                         parseInt(tooltip.css("border-top-width"), 10) +
                         tooltip.height() / 2
                });

            hint.element.css("visibility", "visible");
        },

        hide: function () {
            var hint = this;

            if (hint._hideTimeout) {
                clearTimeout(hint._hideTimeout);
            }

            hint._hideTimeout = setTimeout(function () {
                hint._visible = false;
                hint.element.fadeOut("slow");
            }, hint.options.hideDelay);
        }
    });

    function ClonedObject() { }
    function clone(obj) {
        ClonedObject.prototype = obj;
        return new ClonedObject();
    }

    // Exports ================================================================

    dataviz.ui.plugin(StockChart);

    deepExtend(dataviz, {
        Navigator: Navigator
    });

})(window.kendo.jQuery);





(function ($, undefined) {
    // Imports ===============================================================
    var kendo = window.kendo,
        dataviz = kendo.dataviz,

        Chart = dataviz.ui.Chart,
        ObservableArray = kendo.data.ObservableArray,
        SharedTooltip = dataviz.SharedTooltip,

        deepExtend = kendo.deepExtend,
        isArray = $.isArray,
        inArray = dataviz.inArray,
        math = Math;

    // Constants =============================================================
    var CSS_PREFIX = "k-",
        DEAULT_BAR_WIDTH = 150,
        DEAULT_BULLET_WIDTH = 150,
        BAR = "bar",
        BULLET = "bullet",
        PIE = "pie",
        NO_CROSSHAIR = [BAR, BULLET];

    // Sparkline =============================================================
    var Sparkline = Chart.extend({
        init: function (element, userOptions) {
            var chart = this,
                stage = chart.stage = $("<span />"),
                options = userOptions || {};

            element = $(element)
                .addClass(CSS_PREFIX + "sparkline")
                .empty().append(stage);

            chart._initialWidth = math.floor(element.width());

            options = wrapNumber(options);

            if (isArray(options) || options instanceof ObservableArray) {
                options = { seriesDefaults: { data: options} };
            }

            if (!options.series) {
                options.series = [{ data: wrapNumber(options.data)}];
            }

            deepExtend(options, {
                seriesDefaults: {
                    type: options.type
                }
            });

            if (inArray(options.series[0].type, NO_CROSSHAIR) ||
                inArray(options.seriesDefaults.type, NO_CROSSHAIR)) {
                options = deepExtend({}, {
                    categoryAxis: {
                        crosshair: {
                            visible: false
                        }
                    }
                }, options);
            }

            Chart.fn.init.call(chart, element, options);
        },

        options: {
            name: "Sparkline",
            chartArea: {
                margin: 2
            },
            axisDefaults: {
                visible: false,
                majorGridLines: {
                    visible: false
                },
                valueAxis: {
                    narrowRange: true
                }
            },
            seriesDefaults: {
                type: "line",
                area: {
                    line: {
                        width: 0.5
                    }
                },
                bar: {
                    stack: true
                },
                padding: 2,
                width: 0.5,
                overlay: {
                    gradient: null
                },
                highlight: {
                    visible: false
                },
                border: {
                    width: 0
                },
                markers: {
                    size: 2,
                    visible: false
                }
            },
            tooltip: {
                visible: true,
                shared: true
            },
            categoryAxis: {
                crosshair: {
                    visible: true,
                    tooltip: {
                        visible: false
                    }
                }
            },
            legend: {
                visible: false
            },
            transitions: false,

            pointWidth: 5,

            panes: [{
                clip: false
            }]
        },

        _modelOptions: function () {
            var chart = this,
                chartOptions = chart.options,
                options,
                width = chart._initialWidth,
                stage = chart.stage;

            chart.stage.children().hide();

            var space = $("<span>&nbsp;</span>");
            chart.stage.append(space);

            options = deepExtend({
                width: width ? width : chart._autoWidth(),
                height: stage.height(),
                transitions: chartOptions.transitions
            }, chartOptions.chartArea, {
                inline: true,
                align: false
            });

            stage.css({
                width: options.width,
                height: options.height
            });

            space.remove();

            chart.stage.children().show();
            chart.surface.resize();

            return options;
        },

        _createTooltip: function () {
            var chart = this,
                options = chart.options,
                element = chart.element,
                tooltip;

            if (chart._sharedTooltip()) {
                tooltip = new SparklineSharedTooltip(element, chart._plotArea, options.tooltip);
            } else {
                tooltip = Chart.fn._createTooltip.call(chart);
            }

            return tooltip;
        },

        _surfaceWrap: function () {
            return this.stage;
        },

        _autoWidth: function () {
            var chart = this,
                options = chart.options,
                margin = dataviz.getSpacing(options.chartArea.margin),
                series = options.series,
                dsTotal = chart.dataSource.total(),
                seriesTotal = 0,
                width,
                i,
                currentSeries;

            for (i = 0; i < series.length; i++) {
                currentSeries = series[i];

                if (currentSeries.type === BAR) {
                    return DEAULT_BAR_WIDTH;
                }

                if (currentSeries.type === BULLET) {
                    return DEAULT_BULLET_WIDTH;
                }

                if (currentSeries.type === PIE) {
                    return chart.stage.height();
                }

                if (currentSeries.data) {
                    seriesTotal = math.max(seriesTotal, currentSeries.data.length);
                }
            }

            width = math.max(dsTotal, seriesTotal) * options.pointWidth;
            if (width > 0) {
                width += margin.left + margin.right;
            }

            return width;
        }
    });

    var SparklineSharedTooltip = SharedTooltip.extend({
        options: {
            animation: {
                duration: 0
            }
        },

        _anchor: function (point, slot) {
            var anchor = SharedTooltip.fn._anchor.call(this, point, slot);
            var size = this._measure();
            anchor.y = -size.height - this.options.offset;

            return anchor;
        },

        _hideElement: function () {
            if (this.element) {
                this.element.hide().remove();
            }
        }
    });

    function wrapNumber(x) {
        return typeof x === "number" ? [x] : x;
    }

    // Exports ================================================================

    dataviz.ui.plugin(Sparkline);

    deepExtend(dataviz, {
        SparklineSharedTooltip: SparklineSharedTooltip
    });

})(window.kendo.jQuery);



(function ($, undefined) {
    // Imports ================================================================
    var math = Math,
        abs = math.abs,
        atan = math.atan,
        atan2 = math.atan2,
        cos = math.cos,
        max = math.max,
        min = math.min,
        sin = math.sin,
        tan = math.tan,

        kendo = window.kendo,
        Class = kendo.Class,

        dataviz = kendo.dataviz,
        deepExtend = kendo.deepExtend,

        util = kendo.util,
        defined = util.defined,
        deg = util.deg,
        rad = util.rad,
        round = util.round,
        sqr = util.sqr,
        valueOrDefault = util.valueOrDefault;

    // Implementation =========================================================
    var Location = Class.extend({
        init: function (lat, lng) {
            if (arguments.length === 1) {
                this.lat = lat[0];
                this.lng = lat[1];
            } else {
                this.lat = lat;
                this.lng = lng;
            }
        },

        DISTANCE_ITERATIONS: 100,
        DISTANCE_CONVERGENCE: 1e-12,
        DISTANCE_PRECISION: 2,
        FORMAT: "{0:N6},{1:N6}",

        toArray: function () {
            return [this.lat, this.lng];
        },

        equals: function (loc) {
            return loc && loc.lat === this.lat && loc.lng === this.lng;
        },

        clone: function () {
            return new Location(this.lat, this.lng);
        },

        round: function (precision) {
            this.lng = round(this.lng, precision);
            this.lat = round(this.lat, precision);
            return this;
        },

        wrap: function () {
            this.lng = this.lng % 180;
            this.lat = this.lat % 90;
            return this;
        },

        distanceTo: function (dest, datum) {
            return this.greatCircleTo(dest, datum).distance;
        },

        destination: function (distance, bearing, datum) {
            bearing = rad(bearing);
            datum = datum || dataviz.map.datums.WGS84;

            var fromLat = rad(this.lat);
            var fromLng = rad(this.lng);
            var dToR = distance / kendo.dataviz.map.datums.WGS84.a;

            var lat = math.asin(sin(fromLat) * cos(dToR) +
                                cos(fromLat) * sin(dToR) * cos(bearing));

            var lng = fromLng + atan2(sin(bearing) * sin(dToR) * cos(fromLat),
                                      cos(dToR) - sin(fromLat) * sin(lat));

            return new Location(deg(lat), deg(lng));
        },

        greatCircleTo: function (dest, datum) {
            dest = Location.create(dest);
            datum = datum || dataviz.map.datums.WGS84;

            if (!dest || this.clone().round(8).equals(dest.clone().round(8))) {
                return {
                    distance: 0,
                    azimuthFrom: 0,
                    azimuthTo: 0
                };
            }

            // See http://en.wikipedia.org/wiki/Vincenty's_formulae#Notation
            // o == sigma
            // A == alpha
            var a = datum.a;
            var b = datum.b;
            var f = datum.f;

            var L = rad(dest.lng - this.lng);

            var U1 = atan((1 - f) * tan(rad(this.lat)));
            var sinU1 = sin(U1);
            var cosU1 = cos(U1);

            var U2 = atan((1 - f) * tan(rad(dest.lat)));
            var sinU2 = sin(U2);
            var cosU2 = cos(U2);

            var lambda = L;
            var prevLambda;

            var i = this.DISTANCE_ITERATIONS;
            var converged = false;

            var sinLambda;
            var cosLambda;
            var sino;
            var cosA2;
            var coso;
            var cos2om;
            var sigma;

            while (!converged && i-- > 0) {
                sinLambda = sin(lambda);
                cosLambda = cos(lambda);
                sino = math.sqrt(
                    sqr(cosU2 * sinLambda) + sqr(cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)
                );

                coso = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
                sigma = atan2(sino, coso);

                var sinA = cosU1 * cosU2 * sinLambda / sino;
                cosA2 = 1 - sqr(sinA);
                cos2om = 0;
                if (cosA2 !== 0) {
                    cos2om = coso - 2 * sinU1 * sinU2 / cosA2;
                }

                prevLambda = lambda;
                var C = f / 16 * cosA2 * (4 + f * (4 - 3 * cosA2));
                lambda = L + (1 - C) * f * sinA * (
                    sigma + C * sino * (cos2om + C * coso * (-1 + 2 * sqr(cos2om)))
                );

                converged = abs(lambda - prevLambda) <= this.DISTANCE_CONVERGENCE;
            }

            var u2 = cosA2 * (sqr(a) - sqr(b)) / sqr(b);
            var A = 1 + u2 / 16384 * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)));
            var B = u2 / 1024 * (256 + u2 * (-128 + u2 * (74 - 47 * u2)));
            var deltao = B * sino * (cos2om + B / 4 * (
                coso * (-1 + 2 * sqr(cos2om)) - B / 6 * cos2om * (-3 + 4 * sqr(sino)) * (-3 + 4 * sqr(cos2om))
            ));

            var azimuthFrom = atan2(cosU2 * sinLambda, cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
            var azimuthTo = atan2(cosU1 * sinLambda, -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda);

            return {
                distance: round(b * A * (sigma - deltao), this.DISTANCE_PRECISION),
                azimuthFrom: deg(azimuthFrom),
                azimuthTo: deg(azimuthTo)
            };
        }
    });

    // IE < 9 doesn't allow to override toString on definition
    Location.fn.toString = function () {
        return kendo.format(this.FORMAT, this.lng, this.lat);
    };

    Location.fromLngLat = function (ll) {
        return new Location(ll[1], ll[0]);
    };

    Location.fromLatLng = function (ll) {
        return new Location(ll[0], ll[1]);
    };

    Location.create = function (a, b) {
        if (defined(a)) {
            if (a instanceof Location) {
                return a.clone();
            } else if (arguments.length === 1 && a.length === 2) {
                return Location.fromLatLng(a);
            } else {
                return new Location(a, b);
            }
        }
    };

    var Extent = Class.extend({
        init: function (nw, se) {
            nw = Location.create(nw);
            se = Location.create(se);

            if (nw.lng + 180 > se.lng + 180 &&
                nw.lat + 90 < se.lat + 90) {
                this.se = nw;
                this.nw = se;
            } else {
                this.se = se;
                this.nw = nw;
            }
        },

        contains: function (loc) {
            var nw = this.nw,
                se = this.se,
                lng = valueOrDefault(loc.lng, loc[1]),
                lat = valueOrDefault(loc.lat, loc[0]);

            return loc &&
                   lng + 180 >= nw.lng + 180 &&
                   lng + 180 <= se.lng + 180 &&
                   lat + 90 >= se.lat + 90 &&
                   lat + 90 <= nw.lat + 90;
        },

        center: function () {
            var nw = this.nw;
            var se = this.se;

            var lng = nw.lng + (se.lng - nw.lng) / 2;
            var lat = nw.lat + (se.lat - nw.lat) / 2;
            return new Location(lat, lng);
        },

        containsAny: function (locs) {
            var result = false;
            for (var i = 0; i < locs.length; i++) {
                result = result || this.contains(locs[i]);
            }

            return result;
        },

        include: function (loc) {
            var nw = this.nw,
                se = this.se,
                lng = valueOrDefault(loc.lng, loc[1]),
                lat = valueOrDefault(loc.lat, loc[0]);

            nw.lng = min(nw.lng, lng);
            nw.lat = max(nw.lat, lat);

            se.lng = max(se.lng, lng);
            se.lat = min(se.lat, lat);
        },

        includeAll: function (locs) {
            for (var i = 0; i < locs.length; i++) {
                this.include(locs[i]);
            }
        },

        edges: function () {
            var nw = this.nw,
                se = this.se;

            return { nw: this.nw, ne: new Location(nw.lat, se.lng),
                se: this.se, sw: new Location(se.lat, nw.lng)
            };
        },

        toArray: function () {
            var nw = this.nw,
                se = this.se;

            return [nw, new Location(nw.lat, se.lng),
                    se, new Location(se.lat, nw.lng)];
        },

        overlaps: function (extent) {
            return this.containsAny(extent.toArray()) ||
                   extent.containsAny(this.toArray());
        }
    });

    Extent.World = new Extent([90, -180], [-90, 180]);

    Extent.create = function (a, b) {
        if (a instanceof Extent) {
            return a;
        } else if (a && b) {
            return new Extent(a, b);
        } else if (a && a.length === 4 && !b) {
            return new Extent([a[0], a[1]], [a[2], a[3]]);
        }
    };

    // Exports ================================================================
    deepExtend(dataviz, {
        map: {
            Extent: Extent,
            Location: Location
        }
    });

})(window.kendo.jQuery);

(function ($) {
    var kendo = window.kendo,
        Widget = kendo.ui.Widget,
        template = kendo.template,

        dataviz = kendo.dataviz,
        valueOrDefault = kendo.util.valueOrDefault,
        defined = kendo.util.defined;

    var Attribution = Widget.extend({
        init: function (element, options) {
            Widget.fn.init.call(this, element, options);
            this._initOptions(options);
            this.items = [];
            this.element.addClass("k-widget k-attribution");
        },

        options: {
            name: "Attribution",
            separator: "&nbsp;|&nbsp;",
            itemTemplate: "#= text #"
        },

        filter: function (extent, zoom) {
            this._extent = extent;
            this._zoom = zoom;
            this._render();
        },

        add: function (item) {
            if (defined(item)) {
                if (typeof item === "string") {
                    item = { text: item };
                }

                this.items.push(item);
                this._render();
            }
        },

        remove: function (text) {
            var result = [];
            for (var i = 0; i < this.items.length; i++) {
                var item = this.items[i];
                if (item.text !== text) {
                    result.push(item);
                }
            }

            this.items = result;

            this._render();
        },

        clear: function () {
            this.items = [];
            this.element.empty();
        },

        _render: function () {
            var result = [];
            var itemTemplate = template(this.options.itemTemplate);

            for (var i = 0; i < this.items.length; i++) {
                var item = this.items[i];
                var text = this._itemText(item);
                if (text !== "") {
                    result.push(itemTemplate({
                        text: text
                    }));
                }
            }

            if (result.length > 0) {
                this.element.empty()
                    .append(result.join(this.options.separator))
                    .show();
            } else {
                this.element.hide();
            }
        },

        _itemText: function (item) {
            var text = "";
            var inZoomLevel = this._inZoomLevel(item.minZoom, item.maxZoom);
            var inArea = this._inArea(item.extent);

            if (inZoomLevel && inArea) {
                text += item.text;
            }

            return text;
        },

        _inZoomLevel: function (min, max) {
            var result = true;
            min = valueOrDefault(min, -Number.MAX_VALUE);
            max = valueOrDefault(max, Number.MAX_VALUE);

            result = this._zoom > min && this._zoom < max;

            return result;
        },

        _inArea: function (area) {
            var result = true;

            if (area) {
                result = area.contains(this._extent);
            }

            return result;
        }
    });

    kendo.dataviz.ui.plugin(Attribution);
})(window.kendo.jQuery);

(function ($) {
    var kendo = window.kendo;
    var Widget = kendo.ui.Widget;
    var keys = kendo.keys;
    var proxy = $.proxy;

    var NS = ".kendoNavigator";
    var BUTTONS = button("n") + button("e") + button("s") + button("w");

    var Navigator = Widget.extend({
        init: function (element, options) {
            Widget.fn.init.call(this, element, options);
            this._initOptions(options);

            this.element.addClass("k-widget k-header k-shadow k-navigator")
                        .append(BUTTONS)
                        .on("click" + NS, ".k-button", proxy(this, "_click"));

            var parentElement = this.element.parent().closest("[" + kendo.attr("role") + "]");
            this._keyroot = parentElement.length > 0 ? parentElement : this.element;
            this._tabindex(this._keyroot);

            this._keydown = proxy(this._keydown, this);
            this._keyroot.on("keydown", this._keydown);
        },

        options: {
            name: "Navigator",
            panStep: 1
        },

        events: [
            "pan"
        ],

        dispose: function () {
            this._keyroot.off("keydown", this._keydown);
        },

        _pan: function (x, y) {
            var panStep = this.options.panStep;
            this.trigger("pan", {
                x: x * panStep,
                y: y * panStep
            });
        },

        _click: function (e) {
            var x = 0;
            var y = 0;
            var button = $(e.currentTarget);

            if (button.is(".k-navigator-n")) {
                y = 1;
            } else if (button.is(".k-navigator-s")) {
                y = -1;
            } else if (button.is(".k-navigator-e")) {
                x = 1;
            } else if (button.is(".k-navigator-w")) {
                x = -1;
            }

            this._pan(x, y);
            e.preventDefault();
        },

        _keydown: function (e) {
            switch (e.which) {
                case keys.UP:
                    this._pan(0, 1);
                    e.preventDefault();
                    break;

                case keys.DOWN:
                    this._pan(0, -1);
                    e.preventDefault();
                    break;

                case keys.RIGHT:
                    this._pan(1, 0);
                    e.preventDefault();
                    break;

                case keys.LEFT:
                    this._pan(-1, 0);
                    e.preventDefault();
                    break;
            }
        }
    });

    // Helper functions =======================================================
    function button(dir) {
        return kendo.format(
           '<button class="k-button k-navigator-{0}">' +
               '<span class="k-icon k-i-arrow-{0}"/>' +
           '</button>', dir);
    }

    // Exports ================================================================
    kendo.dataviz.ui.plugin(Navigator);

})(window.kendo.jQuery);

(function ($) {
    var kendo = window.kendo;
    var Widget = kendo.ui.Widget;
    var keys = kendo.keys;
    var proxy = $.proxy;

    var NS = ".kendoZoomControl";
    var BUTTONS = button("in", "+") + button("out", "-");

    var PLUS = 187;
    var MINUS = 189;
    var FF_PLUS = 61;
    var FF_MINUS = 173;

    var ZoomControl = Widget.extend({
        init: function (element, options) {
            Widget.fn.init.call(this, element, options);
            this._initOptions(options);

            this.element.addClass("k-widget k-zoom-control k-button-wrap k-buttons-horizontal")
                        .append(BUTTONS)
                        .on("click" + NS, ".k-button", proxy(this, "_click"));

            var parentElement = this.element.parent().closest("[" + kendo.attr("role") + "]");
            this._keyroot = parentElement.length > 0 ? parentElement : this.element;

            this._tabindex(this._keyroot);

            this._keydown = proxy(this._keydown, this);
            this._keyroot.on("keydown", this._keydown);
        },

        options: {
            name: "ZoomControl",
            zoomStep: 1
        },

        events: [
            "change"
        ],

        _change: function (dir) {
            var zoomStep = this.options.zoomStep;
            this.trigger("change", {
                delta: dir * zoomStep
            });
        },

        _click: function (e) {
            var button = $(e.currentTarget);
            var dir = 1;

            if (button.is(".k-zoom-out")) {
                dir = -1;
            }

            this._change(dir);
            e.preventDefault();
        },

        _keydown: function (e) {
            switch (e.which) {
                case keys.NUMPAD_PLUS:
                case PLUS:
                case FF_PLUS:
                    this._change(1);
                    break;

                case keys.NUMPAD_MINUS:
                case MINUS:
                case FF_MINUS:
                    this._change(-1);
                    break;
            }
        }
    });

    // Helper functions =======================================================
    function button(dir, symbol) {
        return kendo.format(
           '<button class="k-button k-zoom-{0}" title="zoom-{0}">{1}</button>',
           dir, symbol);
    }

    // Exports ================================================================
    kendo.dataviz.ui.plugin(ZoomControl);

})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var math = Math,
        atan = math.atan,
        exp = math.exp,
        pow = math.pow,
        sin = math.sin,
        log = math.log,
        tan = math.tan,

        kendo = window.kendo,
        Class = kendo.Class,

        dataviz = kendo.dataviz,
        deepExtend = kendo.deepExtend,

        g = kendo.geometry,
        Point = g.Point,

        map = dataviz.map,
        Location = map.Location,

        util = kendo.util,
        rad = util.rad,
        deg = util.deg,
        limit = util.limitValue;

    // Constants ==============================================================
    var PI = math.PI,
        PI_DIV_2 = PI / 2,
        PI_DIV_4 = PI / 4,
        DEG_TO_RAD = PI / 180;

    // Coordinate reference systems ===========================================
    var WGS84 = {
        a: 6378137,                 // Semi-major radius
        b: 6356752.314245179,       // Semi-minor radius
        f: 0.0033528106647474805,   // Flattening
        e: 0.08181919084262149      // Eccentricity
    };

    // WGS 84 / World Mercator
    var Mercator = Class.extend({
        init: function (options) {
            this._initOptions(options);
        },

        MAX_LNG: 180,
        MAX_LAT: 85.0840590501,
        INVERSE_ITERATIONS: 15,
        INVERSE_CONVERGENCE: 1e-12,

        options: {
            centralMeridian: 0,
            datum: WGS84
        },

        forward: function (loc, clamp) {
            var proj = this,
                options = proj.options,
                datum = options.datum,
                r = datum.a,
                lng0 = options.centralMeridian,
                lat = limit(loc.lat, -proj.MAX_LAT, proj.MAX_LAT),
                lng = clamp ? limit(loc.lng, -proj.MAX_LNG, proj.MAX_LNG) : loc.lng,
                x = rad(lng - lng0) * r,
                y = proj._projectLat(lat);

            return new Point(x, y);
        },

        _projectLat: function (lat) {
            var datum = this.options.datum,
                ecc = datum.e,
                r = datum.a,
                y = rad(lat),
                ts = tan(PI_DIV_4 + y / 2),
                con = ecc * sin(y),
                p = pow((1 - con) / (1 + con), ecc / 2);

            // See:
            // http://en.wikipedia.org/wiki/Mercator_projection#Generalization_to_the_ellipsoid
            return r * log(ts * p);
        },

        inverse: function (point, clamp) {
            var proj = this,
                options = proj.options,
                datum = options.datum,
                r = datum.a,
                lng0 = options.centralMeridian,
                lng = point.x / (DEG_TO_RAD * r) + lng0,
                lat = limit(proj._inverseY(point.y), -proj.MAX_LAT, proj.MAX_LAT);

            if (clamp) {
                lng = limit(lng, -proj.MAX_LNG, proj.MAX_LNG);
            }

            return new Location(lat, lng);
        },

        _inverseY: function (y) {
            var proj = this,
                datum = proj.options.datum,
                r = datum.a,
                ecc = datum.e,
                ecch = ecc / 2,
                ts = exp(-y / r),
                phi = PI_DIV_2 - 2 * atan(ts),
                i;

            for (i = 0; i <= proj.INVERSE_ITERATIONS; i++) {
                var con = ecc * sin(phi),
                    p = pow((1 - con) / (1 + con), ecch),
                    dphi = PI_DIV_2 - 2 * atan(ts * p) - phi;

                phi += dphi;

                if (math.abs(dphi) <= proj.INVERSE_CONVERGENCE) {
                    break;
                }
            }

            return deg(phi);
        }
    });

    // WGS 84 / Pseudo-Mercator
    // Used by Google Maps, Bing, OSM, etc.
    // Spherical projection of ellipsoidal coordinates.
    var SphericalMercator = Mercator.extend({
        MAX_LAT: 85.0511287798,

        _projectLat: function (lat) {
            var r = this.options.datum.a,
                y = rad(lat),
                ts = tan(PI_DIV_4 + y / 2);

            return r * log(ts);
        },

        _inverseY: function (y) {
            var r = this.options.datum.a,
                ts = exp(-y / r);

            return deg(PI_DIV_2 - (2 * atan(ts)));
        }
    });

    var Equirectangular = Class.extend({
        forward: function (loc) {
            return new Point(loc.lng, loc.lat);
        },

        inverse: function (point) {
            return new Location(point.y, point.x);
        }
    });

    // TODO: Better (less cryptic name) for this class(es)
    var EPSG3857 = Class.extend({
        init: function () {
            var crs = this,
                proj = crs._proj = new SphericalMercator();

            var c = this.c = 2 * PI * proj.options.datum.a;

            // Scale circumference to 1, mirror Y and shift origin to top left
            this._tm = g.transform().translate(0.5, 0.5).scale(1 / c, -1 / c);

            // Inverse transform matrix
            this._itm = g.transform().scale(c, -c).translate(-0.5, -0.5);
        },

        // Location <-> Point (screen coordinates for a given scale)
        toPoint: function (loc, scale, clamp) {
            var point = this._proj.forward(loc, clamp);

            return point
                .transform(this._tm)
                .scale(scale || 1);
        },

        toLocation: function (point, scale, clamp) {
            point = point
                .clone()
                .scale(1 / (scale || 1))
                .transform(this._itm);

            return this._proj.inverse(point, clamp);
        }
    });

    var EPSG3395 = Class.extend({
        init: function () {
            this._proj = new Mercator();
        },

        toPoint: function (loc) {
            return this._proj.forward(loc);
        },

        toLocation: function (point) {
            return this._proj.inverse(point);
        }
    });

    // WGS 84
    var EPSG4326 = Class.extend({
        init: function () {
            this._proj = new Equirectangular();
        },

        toPoint: function (loc) {
            return this._proj.forward(loc);
        },

        toLocation: function (point) {
            return this._proj.inverse(point);
        }
    });

    // Exports ================================================================
    deepExtend(dataviz, {
        map: {
            crs: {
                EPSG3395: EPSG3395,
                EPSG3857: EPSG3857,
                EPSG4326: EPSG4326
            },
            datums: {
                WGS84: WGS84
            },
            projections: {
                Equirectangular: Equirectangular,
                Mercator: Mercator,
                SphericalMercator: SphericalMercator
            }
        }
    });

})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var proxy = $.proxy,
        noop = $.noop,

        kendo = window.kendo,
        Class = kendo.Class,

        dataviz = kendo.dataviz,
        deepExtend = kendo.deepExtend,

        Extent = dataviz.map.Extent,

        util = kendo.util,
        defined = util.defined,
        valueOrDefault = util.valueOrDefault;

    // Implementation =========================================================
    var Layer = Class.extend({
        init: function (map, options) {
            this._initOptions(options);
            this.map = map;

            this.element = $("<div class='k-layer'></div>")
               .css({
                   "zIndex": this.options.zIndex,
                   "opacity": this.options.opacity
               })
               .appendTo(map.scrollElement);

            this._beforeReset = proxy(this._beforeReset, this);
            this._reset = proxy(this._reset, this);
            this._resize = proxy(this._resize, this);
            this._panEnd = proxy(this._panEnd, this);
            this._activate();

            this._updateAttribution();
        },

        destroy: function () {
            this._deactivate();
        },

        show: function () {
            this.reset();
            this._activate();
            this._applyExtent(true);
        },

        hide: function () {
            this._deactivate();
            this._setVisibility(false);
        },

        reset: function () {
            this._beforeReset();
            this._reset();
        },

        _reset: function () {
            this._applyExtent();
        },

        _beforeReset: $.noop,

        _resize: $.noop,

        _panEnd: function () {
            this._applyExtent();
        },

        _applyExtent: function () {
            var options = this.options;

            var zoom = this.map.zoom();
            var matchMinZoom = !defined(options.minZoom) || zoom >= options.minZoom;
            var matchMaxZoom = !defined(options.maxZoom) || zoom <= options.maxZoom;

            var extent = Extent.create(options.extent);
            var inside = !extent || extent.overlaps(this.map.extent());

            this._setVisibility(matchMinZoom && matchMaxZoom && inside);
        },

        _setVisibility: function (visible) {
            this.element.css("display", visible ? "" : "none");
        },

        _activate: function () {
            var map = this.map;
            map.bind("beforeReset", this._beforeReset);
            map.bind("reset", this._reset);
            map.bind("resize", this._resize);
            map.bind("panEnd", this._panEnd);
        },

        _deactivate: function () {
            var map = this.map;
            map.unbind("beforeReset", this._beforeReset);
            map.unbind("reset", this._reset);
            map.unbind("resize", this._resize);
            map.unbind("panEnd", this._panEnd);
        },

        _updateAttribution: function () {
            var attr = this.map.attribution;

            if (attr) {
                attr.add(this.options.attribution);
            }
        }
    });

    // Exports ================================================================
    deepExtend(dataviz, {
        map: {
            layers: {
                Layer: Layer
            }
        }
    });

})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var proxy = $.proxy,

        kendo = window.kendo,
        Class = kendo.Class,
        DataSource = kendo.data.DataSource,

        dataviz = kendo.dataviz,
        deepExtend = kendo.deepExtend,
        last = kendo.util.last,
        defined = kendo.util.defined,

        g = kendo.geometry,

        d = kendo.drawing,
        Group = d.Group,

        map = dataviz.map,
        Location = map.Location,
        Layer = map.layers.Layer;

    // Implementation =========================================================
    var ShapeLayer = Layer.extend({
        init: function (map, options) {
            Layer.fn.init.call(this, map, options);

            this.surface = d.Surface.create(this.element, {
                width: map.scrollElement.width(),
                height: map.scrollElement.height()
            });

            this._initRoot();

            this.movable = new kendo.ui.Movable(this.surface.element);
            this._markers = [];

            this._click = this._handler("shapeClick");
            this.surface.bind("click", this._click);

            this._mouseenter = this._handler("shapeMouseEnter");
            this.surface.bind("mouseenter", this._mouseenter);

            this._mouseleave = this._handler("shapeMouseLeave");
            this.surface.bind("mouseleave", this._mouseleave);

            this._initDataSource();
        },

        options: {
            autoBind: true
        },

        destroy: function () {
            Layer.fn.destroy.call(this);

            this.surface.destroy();
            this.dataSource.unbind("change", this._dataChange);
        },

        setDataSource: function (dataSource) {
            if (this.dataSource) {
                this.dataSource.unbind("change", this._dataChange);
            }

            this.dataSource = dataSource;
            this.dataSource.bind("change", this._dataChange);

            if (this.options.autoBind) {
                this.dataSource.fetch();
            }
        },

        _reset: function () {
            Layer.fn._reset.call(this);
            this._translateSurface();

            if (this._data) {
                this._load(this._data);
            }
        },

        _initRoot: function () {
            this._root = new Group();
            this.surface.draw(this._root);
        },

        _beforeReset: function () {
            this.surface.clear();
            this._initRoot();
        },

        _resize: function () {
            this.surface.size(this.map.size());
        },

        _initDataSource: function () {
            var dsOptions = this.options.dataSource;
            this._dataChange = proxy(this._dataChange, this);
            this.dataSource = DataSource
                .create(dsOptions)
                .bind("change", this._dataChange);

            if (dsOptions && this.options.autoBind) {
                this.dataSource.fetch();
            }
        },

        _dataChange: function (e) {
            this._data = e.sender.view();
            this._load(this._data);
        },

        _load: function (data) {
            this._clearMarkers();

            if (!this._loader) {
                this._loader = new GeoJSONLoader(this.map, this.options.style, this);
            }

            var container = new Group();
            for (var i = 0; i < data.length; i++) {
                var shape = this._loader.parse(data[i]);
                if (shape) {
                    container.append(shape);
                }
            }

            this._root.clear();
            this._root.append(container);
        },

        shapeCreated: function (shape) {
            var cancelled = false;
            if (shape instanceof d.Circle) {
                cancelled = defined(this._createMarker(shape));
            }

            if (!cancelled) {
                var args = { layer: this, shape: shape };
                cancelled = this.map.trigger("shapeCreated", args);
            }

            return cancelled;
        },

        _createMarker: function (shape) {
            var marker = this.map.markers.bind({
                location: shape.location
            }, shape.dataItem);

            if (marker) {
                this._markers.push(marker);
            }

            return marker;
        },

        _clearMarkers: function () {
            for (var i = 0; i < this._markers.length; i++) {
                this.map.markers.remove(this._markers[i]);
            }
            this._markers = [];
        },

        _panEnd: function (e) {
            Layer.fn._panEnd.call(this, e);
            this._translateSurface();
        },

        _translateSurface: function () {
            var map = this.map;
            var nw = map.locationToView(map.extent().nw);

            if (this.surface.translate) {
                this.surface.translate(nw);
                this.movable.moveTo({ x: nw.x, y: nw.y });
            }
        },

        _handler: function (event) {
            var layer = this;
            return function (e) {
                if (e.element) {
                    var args = {
                        layer: layer,
                        shape: e.element,
                        originalEvent: e.originalEvent
                    };

                    layer.map.trigger(event, args);
                }
            };
        }
    });

    var GeoJSONLoader = Class.extend({
        init: function (locator, defaultStyle, observer) {
            this.observer = observer;
            this.locator = locator;
            this.style = defaultStyle;
        },

        parse: function (item) {
            var root = new Group();

            if (item.type === "Feature") {
                this._loadGeometryTo(root, item.geometry, item);
            } else {
                this._loadGeometryTo(root, item, item);
            }

            if (root.children.length < 2) {
                root = root.children[0];
            }

            return root;
        },

        _shapeCreated: function (shape) {
            var cancelled = false;

            if (this.observer && this.observer.shapeCreated) {
                cancelled = this.observer.shapeCreated(shape);
            }

            return cancelled;
        },

        _loadGeometryTo: function (container, geometry, dataItem) {
            var coords = geometry.coordinates;
            var i;
            var path;

            switch (geometry.type) {
                case "LineString":
                    path = this._loadPolygon(container, [coords], dataItem);
                    this._setLineFill(path);
                    break;

                case "MultiLineString":
                    for (i = 0; i < coords.length; i++) {
                        path = this._loadPolygon(container, [coords[i]], dataItem);
                        this._setLineFill(path);
                    }
                    break;

                case "Polygon":
                    this._loadPolygon(container, coords, dataItem);
                    break;

                case "MultiPolygon":
                    for (i = 0; i < coords.length; i++) {
                        this._loadPolygon(container, coords[i], dataItem);
                    }
                    break;

                case "Point":
                    this._loadPoint(container, coords, dataItem);
                    break;

                case "MultiPoint":
                    for (i = 0; i < coords.length; i++) {
                        this._loadPoint(container, coords[i], dataItem);
                    }
                    break;
            }
        },

        _setLineFill: function (path) {
            var segments = path.segments;
            if (segments.length < 4 || !segments[0].anchor().equals(last(segments).anchor())) {
                path.options.fill = null;
            }
        },

        _loadShape: function (container, shape) {
            if (!this._shapeCreated(shape)) {
                container.append(shape);
            }

            return shape;
        },

        _loadPolygon: function (container, rings, dataItem) {
            var shape = this._buildPolygon(rings);
            shape.dataItem = dataItem;

            return this._loadShape(container, shape);
        },

        _buildPolygon: function (rings) {
            var type = rings.length > 1 ? d.MultiPath : d.Path;
            var path = new type(this.style);

            for (var i = 0; i < rings.length; i++) {
                for (var j = 0; j < rings[i].length; j++) {
                    var point = this.locator.locationToView(
                        Location.fromLngLat(rings[i][j])
                    );

                    if (j === 0) {
                        path.moveTo(point.x, point.y);
                    } else {
                        path.lineTo(point.x, point.y);
                    }
                }
            }

            return path;
        },

        _loadPoint: function (container, coords, dataItem) {
            var location = Location.fromLngLat(coords);
            var point = this.locator.locationToView(location);

            var circle = new g.Circle(point, 10);
            var shape = new d.Circle(circle, this.style);
            shape.dataItem = dataItem;
            shape.location = location;

            return this._loadShape(container, shape);
        }
    });

    // Exports ================================================================
    deepExtend(kendo.data, {
        schemas: {
            geojson: {
                type: "json",
                data: function (data) {
                    if (data.type === "FeatureCollection") {
                        return data.features;
                    }

                    if (data.type === "GeometryCollection") {
                        return data.geometries;
                    }

                    return data;
                }
            }
        },
        transports: {
            geojson: {
                read: {
                    dataType: "json"
                }
            }
        }
    });

    deepExtend(dataviz, {
        map: {
            layers: {
                shape: ShapeLayer,
                ShapeLayer: ShapeLayer
            },
            GeoJSONLoader: GeoJSONLoader
        }
    });

})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var proxy = $.proxy,

        kendo = window.kendo,
        getter = kendo.getter,

        dataviz = kendo.dataviz,
        deepExtend = kendo.deepExtend,

        util = kendo.util,
        defined = util.defined,
        isNumber = util.isNumber,

        g = kendo.geometry,
        d = kendo.drawing,

        map = dataviz.map,
        Location = map.Location,
        ShapeLayer = map.layers.ShapeLayer;

    var PI = Math.PI;

    // Implementation =========================================================
    var BubbleLayer = ShapeLayer.extend({
        options: {
            autoBind: true,
            locationField: "location",
            valueField: "value",
            minSize: 0,
            maxSize: 100,
            scale: "sqrt",
            symbol: "circle"
        },

        _load: function (data) {
            if (data.length === 0) {
                return;
            }

            var opt = this.options;
            var getValue = getter(opt.valueField);

            data = data.slice(0);
            data.sort(function (a, b) {
                return getValue(b) - getValue(a);
            });

            var maxValue = getValue(data[0]);
            var scaleType = this._scaleType();
            var scale;
            for (var i = 0; i < data.length; i++) {
                var dataItem = data[i];
                var location = getter(opt.locationField)(dataItem);
                var value = getter(opt.valueField)(dataItem);

                if (defined(location) && defined(value)) {
                    if (!scale) {
                        scale = new scaleType([0, value], [opt.minSize, opt.maxSize]);
                    }

                    location = Location.create(location);
                    var center = this.map.locationToView(location);
                    var size = scale.map(value);

                    var symbol = this._createSymbol({
                        center: center,
                        size: size,
                        style: opt.style,
                        dataItem: dataItem,
                        location: location
                    });

                    symbol.dataItem = dataItem;
                    symbol.location = location;
                    symbol.value = value;

                    this._drawSymbol(symbol);
                }
            }
        },

        _scaleType: function () {
            var scale = this.options.scale;

            if (kendo.isFunction(scale)) {
                return scale;
            }

            return dataviz.map.scales[scale];
        },

        _createSymbol: function (args) {
            var symbol = this.options.symbol;
            if (!kendo.isFunction(symbol)) {
                symbol = dataviz.map.symbols[symbol];
            }

            return symbol(args);
        },

        _drawSymbol: function (shape) {
            var args = { layer: this, shape: shape };
            var cancelled = this.map.trigger("shapeCreated", args);
            if (!cancelled) {
                this.surface.draw(shape);
            }
        }
    });

    var SqrtScale = kendo.Class.extend({
        init: function (domain, range) {
            this._domain = domain;
            this._range = range;

            var domainRange = Math.sqrt(domain[1]) - Math.sqrt(domain[0]);
            var outputRange = range[1] - range[0];
            this._ratio = outputRange / domainRange;
        },

        map: function (value) {
            var rel = (Math.sqrt(value) - Math.sqrt(this._domain[0])) * this._ratio;
            return this._range[0] + rel;
        }
    });

    var Symbols = {
        circle: function (args) {
            var geo = new g.Circle(args.center, args.size / 2);
            return new d.Circle(geo, args.style);
        },

        square: function (args) {
            var path = new d.Path(args.style);
            var halfSize = args.size / 2;
            var center = args.center;

            path.moveTo(center.x - halfSize, center.y - halfSize)
                .lineTo(center.x + halfSize, center.y - halfSize)
                .lineTo(center.x + halfSize, center.y + halfSize)
                .lineTo(center.x - halfSize, center.y + halfSize)
                .close();

            return path;
        }
    };

    // Exports ================================================================
    deepExtend(dataviz, {
        map: {
            layers: {
                bubble: BubbleLayer,
                BubbleLayer: BubbleLayer
            },
            scales: {
                sqrt: SqrtScale
            },
            symbols: Symbols
        }
    });

})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var math = Math,

        proxy = $.proxy,

        kendo = window.kendo,
        Class = kendo.Class,
        template = kendo.template,

        dataviz = kendo.dataviz,
        deepExtend = kendo.deepExtend,

        g = kendo.geometry,
        Point = g.Point,

        Layer = dataviz.map.layers.Layer,

        util = kendo.util,
        round = util.round,
        renderSize = util.renderSize,
        limit = util.limitValue;

    var PAN_DELAY = 100;

    // Image tile layer =============================================================
    var TileLayer = Layer.extend({
        init: function (map, options) {
            Layer.fn.init.call(this, map, options);

            if (typeof this.options.subdomains === "string") {
                this.options.subdomains = this.options.subdomains.split("");
            }

            var viewType = this._viewType();
            this._view = new viewType(this.element, this.options);
        },

        destroy: function () {
            Layer.fn.destroy.call(this);

            this._view.destroy();
            this._view = null;
        },

        _reset: function () {
            Layer.fn._reset.call(this);
            this._updateView();
            this._view.clear();
            this._view.reset();
        },

        _viewType: function () {
            return TileView;
        },

        _activate: function () {
            Layer.fn._activate.call(this);

            if (!kendo.support.mobileOS) {
                if (!this._pan) {
                    this._pan = kendo.throttle(
                        proxy(this._render, this),
                        100
                    );
                }

                this.map.bind("pan", this._pan);
            }
        },

        _deactivate: function () {
            Layer.fn._deactivate.call(this);

            if (this._pan) {
                this.map.unbind("pan", this._pan);
            }
        },

        _updateView: function () {
            var view = this._view,
                map = this.map,
                extent = map.extent(),
                extentToPoint = {
                    nw: map.locationToLayer(extent.nw).round(),
                    se: map.locationToLayer(extent.se).round()
                };

            view.center(map.locationToLayer(map.center()));
            view.extent(extentToPoint);
            view.zoom(map.zoom());
        },

        _resize: function () {
            this._render();
        },

        _panEnd: function (e) {
            Layer.fn._panEnd.call(this, e);
            this._render();
        },

        _render: function () {
            this._updateView();
            this._view.render();
        }
    });

    var TileView = Class.extend({
        init: function (element, options) {
            this.element = element;
            this._initOptions(options);

            this.pool = new TilePool();
        },

        options: {
            tileSize: 256,
            subdomains: ["a", "b", "c"],
            urlTemplate: ""
        },

        center: function (center) {
            this._center = center;
        },

        extent: function (extent) {
            this._extent = extent;
        },

        zoom: function (zoom) {
            this._zoom = zoom;
        },

        pointToTileIndex: function (point) {
            return new Point(
                math.floor(point.x / this.options.tileSize),
                math.floor(point.y / this.options.tileSize)
            );
        },

        clear: function () {
            this.pool.empty();
        },

        tileCount: function () {
            var size = this.size(),
                firstTileIndex = this.pointToTileIndex(this._extent.nw),
                nw = this._extent.nw,
                point = this.indexToPoint(firstTileIndex).translate(-nw.x, -nw.y);

            return {
                x: math.ceil((math.abs(point.x) + size.width) / this.options.tileSize),
                y: math.ceil((math.abs(point.y) + size.height) / this.options.tileSize)
            };
        },

        size: function () {
            var nw = this._extent.nw,
                se = this._extent.se,
                diff = se.clone().translate(-nw.x, -nw.y);

            return {
                width: diff.x,
                height: diff.y
            };
        },

        indexToPoint: function (index) {
            var x = index.x, y = index.y;

            return new Point(
                x * this.options.tileSize,
                y * this.options.tileSize
            );
        },

        subdomainText: function () {
            var subdomains = this.options.subdomains;

            return subdomains[this.subdomainIndex++ % subdomains.length];
        },

        destroy: function () {
            this.element.empty();
            this.pool.empty();
        },

        reset: function () {
            this.subdomainIndex = 0;
            this.basePoint = this._extent.nw;
            this.render();
        },

        render: function () {
            var size = this.tileCount(),
                firstTileIndex = this.pointToTileIndex(this._extent.nw),
                tile, x, y;

            for (x = 0; x < size.x; x++) {
                for (y = 0; y < size.y; y++) {
                    tile = this.createTile({
                        x: firstTileIndex.x + x,
                        y: firstTileIndex.y + y
                    });

                    if (!tile.options.visible) {
                        this.element.append(tile.element);
                        tile.options.visible = true;
                    }
                }
            }
        },

        createTile: function (currentIndex) {
            var options = this.tileOptions(currentIndex);

            return this.pool.get(this._center, options);
        },

        tileOptions: function (currentIndex) {
            var index = this.wrapIndex(currentIndex),
                point = this.indexToPoint(currentIndex),
                base = this.basePoint,
                offset = point.clone().translate(-base.x, -base.y);

            return {
                index: index,
                currentIndex: currentIndex,
                point: point,
                offset: roundPoint(offset),
                zoom: this._zoom,
                subdomain: this.subdomainText(),
                urlTemplate: this.options.urlTemplate,
                errorUrlTemplate: this.options.errorUrlTemplate
            };
        },

        wrapIndex: function (index) {
            var boundary = math.pow(2, this._zoom);
            return {
                x: this.wrapValue(index.x, boundary),
                y: limit(index.y, 0, boundary - 1)
            };
        },

        wrapValue: function (value, boundary) {
            var remainder = (math.abs(value) % boundary);
            if (value >= 0) {
                value = remainder;
            } else {
                value = boundary - (remainder === 0 ? boundary : remainder);
            }

            return value;
        }
    });

    var ImageTile = Class.extend({
        init: function (options) {
            this._initOptions(options);
            this.createElement();
            this.load();
            // initially the image should be
            this.options.visible = false;
        },

        options: {
            urlTemplate: "",
            errorUrlTemplate: "",
            visible: false
        },

        createElement: function () {
            this.element = $("<img style='position: absolute; display: block; visibility: visible;' />")
                            .error(proxy(function (e) {
                                if (this.errorUrl()) {
                                    e.target.setAttribute("src", this.errorUrl());
                                } else {
                                    e.target.removeAttribute("src");
                                }
                            }, this));
        },

        load: function (options) {
            this.options = deepExtend({}, this.options, options);

            var htmlElement = this.element[0];

            htmlElement.style.visibility = "visible";
            htmlElement.style.display = "block";
            htmlElement.style.top = renderSize(this.options.offset.y);
            htmlElement.style.left = renderSize(this.options.offset.x);
            htmlElement.setAttribute("src", this.url());

            this.options.id = tileId(this.options.currentIndex, this.options.zoom);
            this.options.visible = true;
        },

        url: function () {
            var urlResult = template(this.options.urlTemplate);

            return urlResult(this.urlOptions());
        },

        errorUrl: function () {
            var urlResult = template(this.options.errorUrlTemplate);

            return urlResult(this.urlOptions());
        },

        urlOptions: function () {
            var options = this.options;
            return {
                zoom: options.zoom,
                subdomain: options.subdomain,
                z: options.zoom,
                x: options.index.x,
                y: options.index.y,
                s: options.subdomain,
                quadkey: options.quadkey,
                q: options.quadkey,
                culture: options.culture,
                c: options.culture
            };
        },

        destroy: function () {
            if (this.element) {
                this.element.remove();
                this.element = null;
            }
        }
    });

    var TilePool = Class.extend({
        init: function () {
            // calculate max size automaticaly
            this._items = [];
        },

        options: {
            maxSize: 100
        },

        // should considered to remove the center of the screen
        get: function (center, options) {
            var pool = this,
                item;

            if (pool._items.length >= pool.options.maxSize) {
                item = pool._update(center, options);
            } else {
                item = pool._create(options);
            }

            return item;
        },

        empty: function () {
            var items = this._items,
                i;

            for (i = 0; i < items.length; i++) {
                items[i].destroy();
            }

            this._items = [];
        },

        _create: function (options) {
            var pool = this,
                items = pool._items,
                id = tileId(options.currentIndex, options.zoom),
                oldTile, i, item, tile;

            for (i = 0; i < items.length; i++) {
                item = items[i];
                if (item.options.id === id) {
                    oldTile = item;
                    tile = oldTile;
                }
            }

            if (oldTile) {
                oldTile.load(options);
            } else {
                tile = new ImageTile(options);
                this._items.push(tile);
            }

            return tile;
        },

        _update: function (center, options) {
            var pool = this,
                items = pool._items,
                dist = -Number.MAX_VALUE,
                currentDist, index, i, item;

            var id = tileId(options.currentIndex, options.zoom);

            for (i = 0; i < items.length; i++) {
                item = items[i];
                currentDist = item.options.point.clone().distanceTo(center);
                if (item.options.id === id) {
                    return items[i];
                }

                if (dist < currentDist) {
                    index = i;
                    dist = currentDist;
                }
            }

            items[index].load(options);

            return items[index];
        }
    });

    // Methods ================================================================
    function roundPoint(point) {
        return new Point(round(point.x), round(point.y));
    }

    function tileId(index, zoom) {
        return "x:" + index.x + "y:" + index.y + "zoom:" + zoom;
    }

    // Exports ================================================================
    deepExtend(dataviz, {
        map: {
            layers: {
                tile: TileLayer,
                TileLayer: TileLayer,

                ImageTile: ImageTile,
                TilePool: TilePool,
                TileView: TileView
            }
        }
    });

})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var math = Math,

        proxy = $.proxy,

        kendo = window.kendo,
        Class = kendo.Class,
        template = kendo.template,

        dataviz = kendo.dataviz,
        deepExtend = kendo.deepExtend,
        defined = kendo.util.defined,

        Extent = dataviz.map.Extent,
        Location = dataviz.map.Location,
        Layer = dataviz.map.layers.Layer,
        TileLayer = dataviz.map.layers.TileLayer,
        TileView = dataviz.map.layers.TileView;

    // Bing tile layer =============================================================
    var BingLayer = TileLayer.extend({
        init: function (map, options) {
            TileLayer.fn.init.call(this, map, options);

            this._onMetadata = $.proxy(this._onMetadata, this);
            this._fetchMetadata();
        },

        options: {
            baseUrl: "//dev.virtualearth.net/REST/v1/Imagery/Metadata/",
            imagerySet: "road"
        },

        _fetchMetadata: function () {
            var options = this.options;

            if (!options.key) {
                throw new Error("Bing tile layer: API key is required");
            }

            $.ajax({
                url: options.baseUrl + options.imagerySet,
                data: {
                    output: "json",
                    include: "ImageryProviders",
                    key: options.key,
                    uriScheme: this._scheme(window.location.protocol)
                },
                type: "get",
                dataType: "jsonp",
                jsonp: "jsonp",
                success: this._onMetadata
            });
        },

        _scheme: function (proto) {
            return proto.replace(":", "") === "https" ? "https" : "http";
        },

        _onMetadata: function (data) {
            if (data && data.resourceSets.length) {
                var resource = this.resource = data.resourceSets[0].resources[0];

                deepExtend(this._view.options, {
                    urlTemplate: resource.imageUrl
                        .replace("{subdomain}", "#= subdomain #")
                        .replace("{quadkey}", "#= quadkey #")
                        .replace("{culture}", "#= culture #"),
                    subdomains: resource.imageUrlSubdomains
                });

                var options = this.options;
                if (!defined(options.minZoom)) {
                    options.minZoom = resource.zoomMin;
                }
                if (!defined(options.maxZoom)) {
                    options.maxZoom = resource.zoomMax;
                }

                this._addAttribution();
                this.reset();
            }
        },

        _viewType: function () {
            return BingView;
        },

        _addAttribution: function () {
            var attr = this.map.attribution;
            if (attr) {
                var items = this.resource.imageryProviders;
                if (items) {
                    for (var i = 0; i < items.length; i++) {
                        var item = items[i];
                        for (var y = 0; y < item.coverageAreas.length; y++) {
                            var area = item.coverageAreas[y];
                            attr.add({
                                text: item.attribution,
                                minZoom: area.zoomMin,
                                maxZoom: area.zoomMax,
                                extent: new Extent(
                                    new Location(area.bbox[2], area.bbox[1]),
                                    new Location(area.bbox[0], area.bbox[3])
                                )
                            });
                        }
                    }
                }
            }
        },

        imagerySet: function (value) {
            if (value) {
                this.options.imagerySet = value;
                this.map.attribution.clear();
                this._fetchMetadata();
                this._reset();
            } else {
                return this.options.imagerySet;
            }
        }
    });

    var BingView = TileView.extend({
        options: {
            culture: "en-US"
        },

        tileOptions: function (currentIndex) {
            var options = TileView.fn.tileOptions.call(this, currentIndex);

            options.culture = this.options.culture;
            options.quadkey = this.tileQuadKey(this.wrapIndex(currentIndex));

            return options;
        },

        tileQuadKey: function (index) {
            var quadKey = "",
                digit, mask, i;

            for (i = this._zoom; i > 0; i--) {
                digit = 0;
                mask = 1 << (i - 1);

                if ((index.x & mask) !== 0) {
                    digit++;
                }

                if ((index.y & mask) !== 0) {
                    digit += 2;
                }

                quadKey += digit;
            }

            return quadKey;
        }
    });

    // Exports ================================================================
    deepExtend(dataviz, {
        map: {
            layers: {
                bing: BingLayer,
                BingLayer: BingLayer,
                BingView: BingView
            }
        }
    });

})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var doc = document,
        math = Math,
        indexOf = $.inArray,
        proxy = $.proxy,

        kendo = window.kendo,
        Class = kendo.Class,
        DataSource = kendo.data.DataSource,
        Tooltip = kendo.ui.Tooltip,

        dataviz = kendo.dataviz,
        deepExtend = kendo.deepExtend,

        map = dataviz.map,
        Location = map.Location,
        Layer = map.layers.Layer;

    // Implementation =========================================================
    var MarkerLayer = Layer.extend({
        init: function (map, options) {
            Layer.fn.init.call(this, map, options);

            this._markerClick = proxy(this._markerClick, this);
            this.element.on("click", ".k-marker", this._markerClick);

            this.items = [];
            this._initDataSource();
        },

        destroy: function () {
            Layer.fn.destroy.call(this);

            this.element.off("click", ".k-marker", this._markerClick);

            this.dataSource.unbind("change", this._dataChange);
            this.clear();
        },

        options: {
            zIndex: 1000,
            autoBind: true,
            dataSource: {},
            locationField: "location",
            titleField: "title"
        },

        add: function (arg) {
            if ($.isArray(arg)) {
                for (var i = 0; i < arg.length; i++) {
                    this._addOne(arg[i]);
                }
            } else {
                return this._addOne(arg);
            }
        },

        remove: function (marker) {
            marker.destroy();

            var index = indexOf(marker, this.items);
            if (index > -1) {
                this.items.splice(index, 1);
            }
        },

        clear: function () {
            for (var i = 0; i < this.items.length; i++) {
                this.items[i].destroy();
            }

            this.items = [];
        },

        update: function (marker) {
            var loc = marker.location();
            if (loc) {
                marker.showAt(this.map.locationToView(loc));

                var args = { marker: marker, layer: this };
                this.map.trigger("markerActivate", args);
            }
        },

        _reset: function () {
            Layer.fn._reset.call(this);
            var items = this.items;
            for (var i = 0; i < items.length; i++) {
                this.update(items[i]);
            }
        },

        bind: function (options, dataItem) {
            var marker = map.Marker.create(options, this.options);
            marker.dataItem = dataItem;

            var args = { marker: marker, layer: this };
            var cancelled = this.map.trigger("markerCreated", args);
            if (!cancelled) {
                this.add(marker);
                return marker;
            }
        },

        setDataSource: function (dataSource) {
            if (this.dataSource) {
                this.dataSource.unbind("change", this._dataChange);
            }

            this.dataSource = dataSource;
            this.dataSource.bind("change", this._dataChange);

            if (this.options.autoBind) {
                this.dataSource.fetch();
            }
        },

        _addOne: function (arg) {
            var marker = Marker.create(arg, this.options);
            marker.addTo(this);

            return marker;
        },

        _initDataSource: function () {
            var dsOptions = this.options.dataSource;
            this._dataChange = proxy(this._dataChange, this);
            this.dataSource = DataSource
                .create(dsOptions)
                .bind("change", this._dataChange);

            if (dsOptions && this.options.autoBind) {
                this.dataSource.fetch();
            }
        },

        _dataChange: function (e) {
            this._load(e.sender.view());
        },

        _load: function (data) {
            this._data = data;
            this.clear();

            var getLocation = kendo.getter(this.options.locationField);
            var getTitle = kendo.getter(this.options.titleField);
            for (var i = 0; i < data.length; i++) {
                var dataItem = data[i];
                this.bind({
                    location: getLocation(dataItem),
                    title: getTitle(dataItem)
                }, dataItem);
            }
        },

        _markerClick: function (e) {
            var args = { marker: $(e.target).data("kendoMarker"), layer: this };
            this.map.trigger("markerClick", args);
        }
    });

    var Marker = Class.extend({
        init: function (options) {
            this.options = options || {};
        },

        addTo: function (parent) {
            this.layer = parent.markers || parent;
            this.layer.items.push(this);
            this.layer.update(this);
        },

        location: function (value) {
            if (value) {
                this.options.location = Location.create(value).toArray();

                if (this.layer) {
                    this.layer.update(this);
                }

                return this;
            } else {
                return Location.create(this.options.location);
            }
        },

        showAt: function (point) {
            this.render();
            this.element.css({
                left: math.round(point.x),
                top: math.round(point.y)
            });

            if (this.tooltip && this.tooltip.popup) {
                // TODO: Expose popup/tooltip updatePosition? method
                this.tooltip.popup._position();
            }
        },

        hide: function () {
            if (this.element) {
                this.element.remove();
                this.element = null;
            }

            if (this.tooltip) {
                this.tooltip.destroy();
                this.tooltip = null;
            }
        },

        destroy: function () {
            this.layer = null;
            this.hide();
        },

        render: function () {
            if (!this.element) {
                var options = this.options;
                var layer = this.layer;

                this.element = $(doc.createElement("span"))
                    .addClass("k-marker k-marker-" + kendo.toHyphens(options.shape || "pin"))
                    .attr("title", options.title)
                    .data("kendoMarker", this)
                    .css("zIndex", options.zIndex);

                if (layer) {
                    layer.element.append(this.element);
                }

                this.renderTooltip();
            }
        },

        renderTooltip: function () {
            var marker = this;
            var title = marker.options.title;
            var options = marker.options.tooltip || {};

            if (options && Tooltip) {
                var template = options.template;
                if (template) {
                    var contentTemplate = kendo.template(template);
                    options.content = function (e) {
                        e.location = marker.location();
                        e.marker = marker;
                        return contentTemplate(e);
                    };
                }

                if (title || options.content || options.contentUrl) {
                    this.tooltip = new Tooltip(this.element, options);
                    this.tooltip.marker = this;
                }
            }
        }
    });

    Marker.create = function (arg, defaults) {
        if (arg instanceof Marker) {
            return arg;
        }

        return new Marker(deepExtend({}, defaults, arg));
    };

    // Exports ================================================================
    deepExtend(dataviz, {
        map: {
            layers: {
                marker: MarkerLayer,
                MarkerLayer: MarkerLayer
            },
            Marker: Marker
        }
    });

})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var doc = document,
        math = Math,
        min = math.min,
        pow = math.pow,

        proxy = $.proxy,

        kendo = window.kendo,
        Widget = kendo.ui.Widget,
        deepExtend = kendo.deepExtend,

        dataviz = kendo.dataviz,
        ui = dataviz.ui,

        g = kendo.geometry,
        Point = g.Point,

        map = dataviz.map,
        Extent = map.Extent,
        Location = map.Location,
        EPSG3857 = map.crs.EPSG3857,

        util = kendo.util,
        defined = util.defined,
        limit = util.limitValue,
        renderPos = util.renderPos,
        valueOrDefault = util.valueOrDefault;

    // Constants ==============================================================
    var CSS_PREFIX = "k-",
        FRICTION = 0.90,
        FRICTION_MOBILE = 0.93,
        MOUSEWHEEL = "DOMMouseScroll mousewheel",
        VELOCITY_MULTIPLIER = 5;

    // Map widget =============================================================
    var Map = Widget.extend({
        init: function (element, options) {
            kendo.destroy(element);
            Widget.fn.init.call(this, element);

            this._initOptions(options);
            this.bind(this.events, options);

            this.crs = new EPSG3857();

            this.element
                .addClass(CSS_PREFIX + this.options.name.toLowerCase())
                .css("position", "relative")
                .empty()
                .append(doc.createElement("div"));

            this._viewOrigin = this._getOrigin();
            this._initScroller();
            this._initMarkers();
            this._initControls();
            this._initLayers();
            this._reset();

            this._mousewheel = proxy(this._mousewheel, this);
            this.element.bind("click", proxy(this._click, this));
            this.element.bind(MOUSEWHEEL, this._mousewheel);
        },

        options: {
            name: "Map",
            controls: {
                attribution: true,
                navigator: {
                    panStep: 100
                },
                zoom: true
            },
            layers: [],
            layerDefaults: {
                shape: {
                    style: {
                        fill: {
                            color: "#fff"
                        },
                        stroke: {
                            color: "#aaa",
                            width: 0.5
                        }
                    }
                },
                bubble: {
                    style: {
                        fill: {
                            color: "#fff",
                            opacity: 0.5
                        },
                        stroke: {
                            color: "#aaa",
                            width: 0.5
                        }
                    }
                },
                marker: {
                    shape: "pinTarget",
                    tooltip: {
                        position: "top"
                    }
                }
            },
            center: [0, 0],
            zoom: 3,
            minSize: 256,
            minZoom: 1,
            maxZoom: 19,
            markers: [],
            markerDefaults: {
                shape: "pinTarget",
                tooltip: {
                    position: "top"
                }
            },
            wraparound: true
        },

        events: [
            "beforeReset",
            "click",
            "reset",
            "pan",
            "panEnd",
            "markerActivate",
            "markerClick",
            "markerCreated",
            "shapeClick",
            "shapeCreated",
            "shapeMouseEnter",
            "shapeMouseLeave",
            "zoomStart",
            "zoomEnd"
        ],

        destroy: function () {
            this.scroller.destroy();

            if (this.navigator) {
                this.navigator.destroy();
            }

            if (this.attribution) {
                this.attribution.destroy();
            }

            if (this.zoomControl) {
                this.zoomControl.destroy();
            }

            this.markers.destroy();

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

            Widget.fn.destroy.call(this);
        },

        zoom: function (level) {
            var options = this.options;

            if (defined(level)) {
                level = limit(level, options.minZoom, options.maxZoom);
                if (options.zoom !== level) {
                    options.zoom = level;
                    this._reset();
                }

                return this;
            } else {
                return options.zoom;
            }
        },

        center: function (center) {
            if (center) {
                this.options.center = Location.create(center).toArray();
                this._reset();

                return this;
            } else {
                return Location.create(this.options.center);
            }
        },

        extent: function (extent) {
            if (extent) {
                this._setExtent(extent);
                return this;
            } else {
                return this._getExtent();
            }
        },

        setOptions: function (options) {
            Widget.fn.setOptions.call(this, options);
            this._reset();
        },

        locationToLayer: function (location, zoom) {
            var clamp = !this.options.wraparound;
            location = Location.create(location);
            return this.crs.toPoint(location, this._layerSize(zoom), clamp);
        },

        layerToLocation: function (point, zoom) {
            var clamp = !this.options.wraparound;
            point = Point.create(point);
            return this.crs.toLocation(point, this._layerSize(zoom), clamp);
        },

        locationToView: function (location) {
            location = Location.create(location);
            var origin = this.locationToLayer(this._viewOrigin);
            var point = this.locationToLayer(location);

            return point.translateWith(origin.scale(-1));
        },

        viewToLocation: function (point, zoom) {
            var origin = this.locationToLayer(this._getOrigin(), zoom);
            point = Point.create(point);
            point = point.clone().translateWith(origin);
            return this.layerToLocation(point, zoom);
        },

        eventOffset: function (e) {
            var offset = this.element.offset();
            var event = e.originalEvent || e;
            var x = valueOrDefault(event.pageX, event.clientX) - offset.left;
            var y = valueOrDefault(event.pageY, event.clientY) - offset.top;

            return new g.Point(x, y);
        },

        eventToView: function (e) {
            var cursor = this.eventOffset(e);
            return this.locationToView(this.viewToLocation(cursor));
        },

        eventToLayer: function (e) {
            return this.locationToLayer(this.eventToLocation(e));
        },

        eventToLocation: function (e) {
            var cursor = this.eventOffset(e);
            return this.viewToLocation(cursor);
        },

        viewSize: function () {
            var element = this.element;
            var scale = this._layerSize();
            var width = element.width();

            if (!this.options.wraparound) {
                width = min(scale, width);
            }
            return {
                width: width,
                height: min(scale, element.height())
            };
        },

        _setOrigin: function (origin, zoom) {
            var size = this.viewSize(),
                topLeft;

            origin = this._origin = Location.create(origin);
            topLeft = this.locationToLayer(origin, zoom);
            topLeft.x += size.width / 2;
            topLeft.y += size.height / 2;

            this.options.center = this.layerToLocation(topLeft, zoom).toArray();

            return this;
        },

        _getOrigin: function (invalidate) {
            var size = this.viewSize(),
                topLeft;

            if (invalidate || !this._origin) {
                topLeft = this.locationToLayer(this.center());
                topLeft.x -= size.width / 2;
                topLeft.y -= size.height / 2;

                this._origin = this.layerToLocation(topLeft);
            }

            return this._origin;
        },

        _setExtent: function (extent) {
            extent = Extent.create(extent);
            this.center(extent.center());

            var width = this.element.width();
            var height = this.element.height();
            for (var zoom = this.options.maxZoom; zoom >= this.options.minZoom; zoom--) {
                var nw = this.locationToLayer(extent.nw, zoom);
                var se = this.locationToLayer(extent.se, zoom);
                var layerWidth = math.abs(se.x - nw.x);
                var layerHeight = math.abs(se.y - nw.y);

                if (layerWidth <= width && layerHeight <= height) {
                    break;
                }
            }

            this.zoom(zoom);
        },

        _getExtent: function () {
            var nw = this._getOrigin();
            var bottomRight = this.locationToLayer(nw);
            var size = this.viewSize();

            bottomRight.x += size.width;
            bottomRight.y += size.height;

            var se = this.layerToLocation(bottomRight);
            return new Extent(nw, se);
        },

        _zoomAround: function (pivot, level) {
            this._setOrigin(this.layerToLocation(pivot, level), level);
            this.zoom(level);
        },

        _initControls: function () {
            var controls = this.options.controls;

            if (ui.Attribution && controls.attribution) {
                this._createAttribution(controls.attribution);
            }

            if (!kendo.support.mobileOS) {
                if (ui.Navigator && controls.navigator) {
                    this._createNavigator(controls.navigator);
                }

                if (ui.ZoomControl && controls.zoom) {
                    this._createZoomControl(controls.zoom);
                }
            }
        },

        _createControlElement: function (options, defaultPos) {
            var pos = options.position || defaultPos;
            var posSelector = "." + renderPos(pos).replace(" ", ".");
            var wrap = $(".k-map-controls" + posSelector, this.element);
            if (wrap.length === 0) {
                wrap = $("<div>")
                       .addClass("k-map-controls " + renderPos(pos))
                       .appendTo(this.element);
            }

            return $("<div>").appendTo(wrap);
        },

        _createAttribution: function (options) {
            var element = this._createControlElement(options, "bottomRight");
            this.attribution = new ui.Attribution(element, options);
        },

        _createNavigator: function (options) {
            var element = this._createControlElement(options, "topLeft");
            var navigator = this.navigator = new ui.Navigator(element, options);

            this._navigatorPan = proxy(this._navigatorPan, this);
            navigator.bind("pan", this._navigatorPan);

            this._navigatorCenter = proxy(this._navigatorCenter, this);
            navigator.bind("center", this._navigatorCenter);
        },

        _navigatorPan: function (e) {
            var map = this;
            var scroller = map.scroller;

            var x = scroller.scrollLeft + e.x;
            var y = scroller.scrollTop - e.y;

            var bounds = this._virtualSize;
            var height = this.element.height();
            var width = this.element.width();

            // TODO: Move limits in scroller
            x = limit(x, bounds.x.min, bounds.x.max - width);
            y = limit(y, bounds.y.min, bounds.y.max - height);

            map.scroller.one("scroll", function (e) { map._scrollEnd(e); });
            map.scroller.scrollTo(-x, -y);
        },

        _navigatorCenter: function () {
            this.center(this.options.center);
        },

        _createZoomControl: function (options) {
            var element = this._createControlElement(options, "topLeft");
            var zoomControl = this.zoomControl = new ui.ZoomControl(element, options);

            this._zoomControlChange = proxy(this._zoomControlChange, this);
            zoomControl.bind("change", this._zoomControlChange);
        },

        _zoomControlChange: function (e) {
            if (!this.trigger("zoomStart", { originalEvent: e })) {
                this.zoom(this.zoom() + e.delta);
                this.trigger("zoomEnd", { originalEvent: e });
            }
        },

        _initScroller: function () {
            var friction = kendo.support.mobileOS ? FRICTION_MOBILE : FRICTION;
            var zoomable = this.options.zoomable !== false;
            var scroller = this.scroller = new kendo.mobile.ui.Scroller(
                this.element.children(0), {
                    friction: friction,
                    velocityMultiplier: VELOCITY_MULTIPLIER,
                    zoom: zoomable,
                    mousewheelScrolling: false
                });

            scroller.bind("scroll", proxy(this._scroll, this));
            scroller.bind("scrollEnd", proxy(this._scrollEnd, this));
            scroller.userEvents.bind("gesturestart", proxy(this._scaleStart, this));
            scroller.userEvents.bind("gestureend", proxy(this._scale, this));

            this.scrollElement = scroller.scrollElement;
        },

        _initLayers: function () {
            var defs = this.options.layers,
                layers = this.layers = [];

            for (var i = 0; i < defs.length; i++) {
                var options = defs[i];
                var type = options.type || "shape";
                var defaults = this.options.layerDefaults[type];
                var impl = dataviz.map.layers[type];

                layers.push(new impl(this, deepExtend({}, defaults, options)));
            }
        },

        _initMarkers: function () {
            this.markers = new map.layers.MarkerLayer(this, this.options.markerDefaults);
            this.markers.add(this.options.markers);
        },

        _scroll: function (e) {
            var origin = this.locationToLayer(this._viewOrigin).round();
            var movable = e.sender.movable;

            var offset = new g.Point(movable.x, movable.y).scale(-1).scale(1 / movable.scale);
            origin.x += offset.x;
            origin.y += offset.y;

            this._setOrigin(this.layerToLocation(origin));
            this.trigger("pan", {
                originalEvent: e,
                origin: this._getOrigin(),
                center: this.center()
            });
        },

        _scrollEnd: function (e) {
            this.trigger("panEnd", {
                originalEvent: e,
                origin: this._getOrigin(),
                center: this.center()
            });
        },

        _scaleStart: function (e) {
            if (this.trigger("zoomStart", { originalEvent: e })) {
                var touch = e.touches[1];
                if (touch) {
                    touch.cancel();
                }
            }
        },

        _scale: function (e) {
            var scale = this.scroller.movable.scale;
            var zoom = this._scaleToZoom(scale);
            var gestureCenter = new g.Point(e.center.x, e.center.y);
            var centerLocation = this.viewToLocation(gestureCenter, zoom);
            var centerPoint = this.locationToLayer(centerLocation, zoom);
            var originPoint = centerPoint.translate(-gestureCenter.x, -gestureCenter.y);

            this._zoomAround(originPoint, zoom);
            this.trigger("zoomEnd", { originalEvent: e });
        },

        _scaleToZoom: function (scaleDelta) {
            var scale = this._layerSize() * scaleDelta;
            var tiles = scale / this.options.minSize;
            var zoom = math.log(tiles) / math.log(2);

            return math.round(zoom);
        },

        _reset: function () {
            if (this.attribution) {
                this.attribution.filter(this.center(), this.zoom());
            }

            this._viewOrigin = this._getOrigin(true);
            this._resetScroller();
            this.trigger("beforeReset");
            this.trigger("reset");
        },

        _resetScroller: function () {
            var scroller = this.scroller;
            var x = scroller.dimensions.x;
            var y = scroller.dimensions.y;
            var scale = this._layerSize();
            var maxScale = 20 * scale;
            var nw = this.extent().nw;
            var topLeft = this.locationToLayer(nw).round();

            scroller.movable.round = true;
            scroller.reset();
            scroller.userEvents.cancel();

            var maxZoom = this.options.maxZoom - this.zoom();
            scroller.dimensions.maxScale = pow(2, maxZoom);

            var xBounds = { min: -topLeft.x, max: scale - topLeft.x };
            var yBounds = { min: -topLeft.y, max: scale - topLeft.y };

            if (this.options.wraparound) {
                xBounds.min = -maxScale;
                xBounds.max = maxScale;
            }

            if (this.options.pannable === false) {
                var viewSize = this.viewSize();
                xBounds.min = yBounds.min = 0;
                xBounds.max = viewSize.width;
                yBounds.max = viewSize.height;
            }

            x.makeVirtual();
            y.makeVirtual();
            x.virtualSize(xBounds.min, xBounds.max);
            y.virtualSize(yBounds.min, yBounds.max);

            this._virtualSize = { x: xBounds, y: yBounds };
        },

        _renderLayers: function () {
            var defs = this.options.layers,
                layers = this.layers = [],
                scrollWrap = this.scrollWrap;

            scrollWrap.empty();

            for (var i = 0; i < defs.length; i++) {
                var options = defs[i];
                var type = options.type || "shape";
                var defaults = this.options.layerDefaults[type];
                var impl = dataviz.map.layers[type];

                layers.push(new impl(this, deepExtend({}, defaults, options)));
            }
        },

        _layerSize: function (zoom) {
            zoom = valueOrDefault(zoom, this.options.zoom);
            return this.options.minSize * pow(2, zoom);
        },

        _click: function (e) {
            var cursor = this.eventOffset(e);
            this.trigger("click", {
                originalEvent: e,
                location: this.viewToLocation(cursor)
            });
        },

        _mousewheel: function (e) {
            e.preventDefault();
            var delta = dataviz.mwDelta(e) > 0 ? -1 : 1;
            var options = this.options;
            var fromZoom = this.zoom();
            var toZoom = limit(fromZoom + delta, options.minZoom, options.maxZoom);

            if (options.zoomable !== false && toZoom !== fromZoom) {
                if (!this.trigger("zoomStart", { originalEvent: e })) {
                    var cursor = this.eventOffset(e);
                    var location = this.viewToLocation(cursor);
                    var postZoom = this.locationToLayer(location, toZoom);
                    var origin = postZoom.translate(-cursor.x, -cursor.y);
                    this._zoomAround(origin, toZoom);

                    this.trigger("zoomEnd", { originalEvent: e });
                }
            }
        }
    });

    // Exports ================================================================
    dataviz.ui.plugin(Map);

})(window.kendo.jQuery);



(function ($, undefined) {
    var kendo = window.kendo,
        diagram = kendo.dataviz.diagram = {},
        Class = kendo.Class,
        deepExtend = kendo.deepExtend,
        isArray = $.isArray,
        EPSILON = 1e-06;

    /*-------------------Diverse utilities----------------------------*/
    var Utils = {
    };

    deepExtend(Utils, {
        isNearZero: function (num) {
            return Math.abs(num) < EPSILON;
        },
        isDefined: function (obj) {
            return typeof obj !== 'undefined';
        },

        isUndefined: function (obj) {
            return (typeof obj === 'undefined') || obj === null;
        },
        /**
        * Returns whether the given object is an object or a value.
        */
        isObject: function (obj) {
            return obj === Object(obj);
        },
        /**
        * Returns whether the object has a property with the given name.
        */
        has: function (obj, key) {
            return Object.hasOwnProperty.call(obj, key);
        },
        /**
        * Returns whether the given object is a string.
        */
        isString: function (obj) {
            return Object.prototype.toString.call(obj) == '[object String]';
        },
        isBoolean: function (obj) {
            return Object.prototype.toString.call(obj) == '[object Boolean]';
        },
        isType: function (obj, type) {
            return Object.prototype.toString.call(obj) == '[object ' + type + ']';
        },
        /**
        * Returns whether the given object is a number.
        */
        isNumber: function (obj) {
            return !isNaN(parseFloat(obj)) && isFinite(obj);
        },
        /**
        * Return whether the given object (array or dictionary).
        */
        isEmpty: function (obj) {
            if (obj === null) {
                return true;
            }
            if (isArray(obj) || Utils.isString(obj)) {
                return obj.length === 0;
            }
            for (var key in obj) {
                if (Utils.has(obj, key)) {
                    return false;
                }
            }
            return true;
        },
        simpleExtend: function (destination, source) {
            if (!Utils.isObject(source)) {
                return;
            }

            for (var name in source) {
                destination[name] = source[name];
            }
        },
        /**
        * Returns an array of the specified size and with each entry set to the given value.
        * @param size
        * @param value
        * @returns {Array}
        */
        initArray: function createIdArray(size, value) {
            var array = [];
            for (var i = 0; i < size; ++i) {
                array[i] = value;
            }
            return array;
        },
        serializePoints: function (points) {
            var res = [];
            for (var i = 0; i < points.length; i++) {
                var p = points[i];
                res.push(p.x + ";" + p.y);
            }
            return res.join(";");
        },
        deserializePoints: function (s) {
            var v = s.split(";"), points = [];
            if (v.length % 2 !== 0) {
                throw "Not an array of points.";
            }
            for (var i = 0; i < v.length; i += 2) {
                points.push(new diagram.Point(
                    parseInt(v[i], 10),
                    parseInt(v[i + 1], 10)
                ));
            }
            return points;
        },
        /**
        * Returns an integer within the given bounds.
        * @param lower The inclusive lower bound.
        * @param upper The exclusive upper bound.
        * @returns {number}
        */
        randomInteger: function (lower, upper) {
            return parseInt(Math.floor(Math.random() * upper) + lower, 10);
        },
        /*
        Depth-first traversal of the given node.
        */
        DFT: function (el, func) {
            func(el);
            if (el.childNodes) {
                for (var i = 0; i < el.childNodes.length; i++) {
                    var item = el.childNodes[i];
                    this.DFT(item, func);
                }
            }
        },
        /*
        Returns the angle in degrees for the given matrix
        */
        getMatrixAngle: function (m) {
            if (m === null || m.d === 0) {
                return 0;
            }
            return Math.atan2(m.b, m.d) * 180 / Math.PI;
        },

        /*
        Returns the scaling factors for the given matrix.
        */
        getMatrixScaling: function (m) {
            var sX = Math.sqrt(m.a * m.a + m.c * m.c);
            var sY = Math.sqrt(m.b * m.b + m.d * m.d);
            return [sX, sY];
        }

    });

    /**
    * The Range defines an array of equally separated numbers.
    * @param start The start-value of the Range.
    * @param stop The end-value of the Range.
    * @param step The separation between the values (default:1).
    * @returns {Array}
    */
    function Range(start, stop, step) {
        if (typeof start == 'undefined' || typeof stop == 'undefined') {
            return [];
        }
        if (step && Utils.sign(stop - start) != Utils.sign(step)) {
            throw "The sign of the increment should allow to reach the stop-value.";
        }
        step = step || 1;
        start = start || 0;
        stop = stop || start;
        if ((stop - start) / step === Infinity) {
            throw "Infinite range defined.";
        }
        var range = [], i = -1, j;

        function rangeIntegerScale(x) {
            var k = 1;
            while (x * k % 1) {
                k *= 10;
            }
            return k;
        }

        var k = rangeIntegerScale(Math.abs(step));
        start *= k;
        stop *= k;
        step *= k;
        if (start > stop && step > 0) {
            step = -step;
        }
        if (step < 0) {
            while ((j = start + step * ++i) >= stop) {
                range.push(j / k);
            }
        }
        else {
            while ((j = start + step * ++i) <= stop) {
                range.push(j / k);
            }
        }
        return range;
    }

    /*-------------------Diverse math functions----------------------------*/

    function findRadian(start, end) {
        if (start == end) {
            return 0;
        }
        var sngXComp = end.x - start.x,
            sngYComp = start.y - end.y,
            atan = Math.atan(sngXComp / sngYComp);
        if (sngYComp >= 0) {
            return sngXComp < 0 ? atan + (2 * Math.PI) : atan;
        }
        return atan + Math.PI;
    }

    Utils.sign = function (number) {
        return number ? number < 0 ? -1 : 1 : 0;
    };

    Utils.findAngle = function (center, end) {
        return findRadian(center, end) * 180 / Math.PI;
    };

    /*-------------------Array Helpers ----------------------------*/

    Utils.forEach = function (arr, iterator, thisRef) {
        for (var i = 0; i < arr.length; i++) {
            iterator.call(thisRef, arr[i], i, arr);
        }
    };

    Utils.any = function (arr, predicate) {
        for (var i = 0; i < arr.length; ++i) {
            if (predicate(arr[i])) {
                return arr[i];
            }
        }
        return null;
    };

    Utils.remove = function (arr, what) {
        var ax;
        while ((ax = Utils.indexOf(arr, what)) !== -1) {
            arr.splice(ax, 1);
        }
        return arr;
    };

    Utils.contains = function (arr, obj) {
        return Utils.indexOf(arr, obj) !== -1;
    };

    Utils.indexOf = function (arr, what) {
        return $.inArray(what, arr);
    };

    Utils.fold = function (list, iterator, acc, context) {
        var initial = arguments.length > 2;

        for (var i = 0; i < list.length; i++) {
            var value = list[i];
            if (!initial) {
                acc = value;
                initial = true;
            }
            else {
                acc = iterator.call(context, acc, value, i, list);
            }
        }

        if (!initial) {
            throw 'Reduce of empty array with no initial value';
        }

        return acc;
    };

    Utils.find = function (arr, iterator, context) {
        var result;
        Utils.any(arr, function (value, index, list) {
            if (iterator.call(context, value, index, list)) {
                result = value;
                return true;
            }
            return false;
        });
        return result;
    };

    Utils.first = function (arr, constraint, context) {
        if (arr.length === 0) {
            return null;
        }
        if (Utils.isUndefined(constraint)) {
            return arr[0];
        }

        return Utils.find(arr, constraint, context);
    };

    /**
    * Inserts the given element at the specified position and returns the result.
    */
    Utils.insert = function (arr, element, position) {
        arr.splice(position, 0, element);
        return arr;
    };

    Utils.all = function (arr, iterator, context) {
        var result = true;
        var value;

        for (var i = 0; i < arr.length; i++) {
            value = arr[i];
            result = result && iterator.call(context, value, i, arr);

            if (!result) {
                break;
            }
        }

        return result;
    };

    Utils.clear = function (arr) {
        arr.splice(0, arr.length);
    };

    /**
    * Sort the arrays on the basis of the first one (considered as keys and the other array as values).
    * @param a
    * @param b
    * @param sortfunc (optiona) sorting function for the values in the first array
    */
    Utils.bisort = function (a, b, sortfunc) {
        if (Utils.isUndefined(a)) {
            throw "First array is not specified.";
        }
        if (Utils.isUndefined(b)) {
            throw "Second array is not specified.";
        }
        if (a.length != b.length) {
            throw "The two arrays should have equal length";
        }

        var all = [], i;

        for (i = 0; i < a.length; i++) {
            all.push({ 'x': a[i], 'y': b[i] });
        }
        if (Utils.isUndefined(sortfunc)) {
            all.sort(function (m, n) {
                return m.x - n.x;
            });
        }
        else {
            all.sort(function (m, n) {
                return sortfunc(m.x, n.x);
            });
        }

        Utils.clear(a);
        Utils.clear(b);

        for (i = 0; i < all.length; i++) {
            a.push(all[i].x);
            b.push(all[i].y);
        }
    };

    Utils.addRange = function (arr, range) {
        arr.push.apply(arr, range);
    };

    var Easing = {
        easeInOut: function (pos) {
            return ((-Math.cos(pos * Math.PI) / 2) + 0.5);
        }
    };

    /**
    * An animation ticker driving an adapter which sets a particular
    * property in function of the tick.
    * @type {*}
    */
    var Ticker = kendo.Class.extend({
        init: function () {
            this.adapters = [];
            this.target = 0;
            this.tick = 0;
            this.interval = 20;
            this.duration = 800;
            this.lastTime = null;
            this.handlers = [];
            var _this = this;
            this.transition = Easing.easeInOut;
            this.timerDelegate = function () {
                _this.onTimerEvent();
            };
        },
        addAdapter: function (a) {
            this.adapters.push(a);
        },
        onComplete: function (handler) {
            this.handlers.push(handler);
        },
        removeHandler: function (handler) {
            this.handlers = $.grep(this.handlers, function (h) {
                return h !== handler;
            });
        },
        trigger: function () {
            var _this = this;
            if (this.handlers) {
                Utils.forEach(this.handlers, function (h) {
                    return h.call(_this.caller !== null ? _this.caller : _this);
                });
            }
        },
        onStep: function () {
        },
        seekTo: function (to) {
            this.seekFromTo(this.tick, to);
        },
        seekFromTo: function (from, to) {
            this.target = Math.max(0, Math.min(1, to));
            this.tick = Math.max(0, Math.min(1, from));
            this.lastTime = new Date().getTime();
            if (!this.intervalId) {
                this.intervalId = window.setInterval(this.timerDelegate, this.interval);
            }
        },
        stop: function () {
            if (this.intervalId) {
                window.clearInterval(this.intervalId);
                this.intervalId = null;

                //this.trigger.call(this);
                this.trigger();
                // this.next();
            }
        },
        play: function (origin) {
            if (this.adapters.length === 0) {
                return;
            }
            if (origin !== null) {
                this.caller = origin;
            }
            this.initState();
            this.seekFromTo(0, 1);
        },
        reverse: function () {
            this.seekFromTo(1, 0);
        },
        initState: function () {
            if (this.adapters.length === 0) {
                return;
            }
            for (var i = 0; i < this.adapters.length; i++) {
                this.adapters[i].initState();
            }
        },
        propagate: function () {
            var value = this.transition(this.tick);

            for (var i = 0; i < this.adapters.length; i++) {
                this.adapters[i].update(value);
            }
        },
        onTimerEvent: function () {
            var now = new Date().getTime();
            var timePassed = now - this.lastTime;
            this.lastTime = now;
            var movement = (timePassed / this.duration) * (this.tick < this.target ? 1 : -1);
            if (Math.abs(movement) >= Math.abs(this.tick - this.target)) {
                this.tick = this.target;
            } else {
                this.tick += movement;
            }

            try {
                this.propagate();
            } finally {
                this.onStep.call(this);
                if (this.target == this.tick) {
                    this.stop();
                }
            }
        }
    });

    kendo.deepExtend(diagram, {
        init: function (element) {
            kendo.init(element, diagram.ui);
        },

        Utils: Utils,
        Range: Range,
        Ticker: Ticker
    });
})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var kendo = window.kendo,
        diagram = kendo.dataviz.diagram,
        Class = kendo.Class,
        deepExtend = kendo.deepExtend,
        dataviz = kendo.dataviz,
        Utils = diagram.Utils,
        Point = dataviz.Point2D,
        isFunction = kendo.isFunction,
        contains = Utils.contains,
        map = $.map;

    // Constants ==============================================================
    var HITTESTAREA = 3,
        EPSILON = 1e-06;

    deepExtend(Point.fn, {
        plus: function (p) {
            return new Point(this.x + p.x, this.y + p.y);
        },
        minus: function (p) {
            return new Point(this.x - p.x, this.y - p.y);
        },
        offset: function (value) {
            return new Point(this.x - value, this.y - value);
        },
        times: function (s) {
            return new Point(this.x * s, this.y * s);
        },
        normalize: function () {
            if (this.length() === 0) {
                return new Point();
            }
            return this.times(1 / this.length());
        },
        length: function () {
            return Math.sqrt(this.x * this.x + this.y * this.y);
        },
        toString: function () {
            return "(" + this.x + "," + this.y + ")";
        },
        lengthSquared: function () {
            return (this.x * this.x + this.y * this.y);
        },
        middleOf: function MiddleOf(p, q) {
            return new Point(q.x - p.x, q.y - p.y).times(0.5).plus(p);
        },
        toPolar: function (useDegrees) {
            var factor = 1;
            if (useDegrees) {
                factor = 180 / Math.PI;
            }
            var a = Math.atan2(Math.abs(this.y), Math.abs(this.x));
            var halfpi = Math.PI / 2;
            var len = this.length();
            if (this.x === 0) {
                // note that the angle goes down and not the usual mathematical convention

                if (this.y === 0) {
                    return new Polar(0, 0);
                }
                if (this.y > 0) {
                    return new Polar(len, factor * halfpi);
                }
                if (this.y < 0) {
                    return new Polar(len, factor * 3 * halfpi);
                }
            }
            else if (this.x > 0) {
                if (this.y === 0) {
                    return new Polar(len, 0);
                }
                if (this.y > 0) {
                    return new Polar(len, factor * a);
                }
                if (this.y < 0) {
                    return new Polar(len, factor * (4 * halfpi - a));
                }
            }
            else {
                if (this.y === 0) {
                    return new Polar(len, 2 * halfpi);
                }
                if (this.y > 0) {
                    return new Polar(len, factor * (2 * halfpi - a));
                }
                if (this.y < 0) {
                    return new Polar(len, factor * (2 * halfpi + a));
                }
            }
        },
        isOnLine: function (from, to) {
            if (from.x > to.x) { // from must be the leftmost point
                var temp = to;
                to = from;
                from = temp;
            }
            var r1 = new Rect(from.x, from.y).inflate(HITTESTAREA, HITTESTAREA),
                r2 = new Rect(to.x, to.y).inflate(HITTESTAREA, HITTESTAREA), o1, u1;
            if (r1.union(r2).contains(this)) {
                if (from.x === to.x || from.y === to.y) {
                    return true;
                }
                else if (from.y < to.y) {
                    o1 = r1.x + (((r2.x - r1.x) * (this.y - (r1.y + r1.height))) / ((r2.y + r2.height) - (r1.y + r1.height)));
                    u1 = (r1.x + r1.width) + ((((r2.x + r2.width) - (r1.x + r1.width)) * (this.y - r1.y)) / (r2.y - r1.y));
                }
                else {
                    o1 = r1.x + (((r2.x - r1.x) * (this.y - r1.y)) / (r2.y - r1.y));
                    u1 = (r1.x + r1.width) + ((((r2.x + r2.width) - (r1.x + r1.width)) * (this.y - (r1.y + r1.height))) / ((r2.y + r2.height) - (r1.y + r1.height)));
                }
                return (this.x > o1 && this.x < u1);
            }
            return false;
        }
    });

    deepExtend(Point, {
        parse: function (str) {
            var tempStr = str.slice(1, str.length - 1),
                xy = tempStr.split(","),
                x = parseInt(xy[0], 10),
                y = parseInt(xy[1], 10);
            if (!isNaN(x) && !isNaN(y)) {
                return new Point(x, y);
            }
        }
    });

    /**
    * Structure combining a Point with two additional points representing the handles or tangents attached to the first point.
    * If the additional points are null or equal to the first point the path will be sharp.
    * Left and right correspond to the direction of the underlying path.
    */
    var PathDefiner = Class.extend(
        {
            init: function (p, left, right) {
                this.point = p;
                this.left = left;
                this.right = right;
            }
        }
    );

    /**
    * Defines a rectangular region.
    */
    var Rect = Class.extend({
        init: function (x, y, width, height) {
            this.x = x || 0;
            this.y = y || 0;
            this.width = width || 0;
            this.height = height || 0;
        },
        contains: function (point) {
            return ((point.x >= this.x) && (point.x <= (this.x + this.width)) && (point.y >= this.y) && (point.y <= (this.y + this.height)));
        },
        inflate: function (dx, dy) {
            if (dy === undefined) {
                dy = dx;
            }

            this.x -= dx;
            this.y -= dy;
            this.width += 2 * dx + 1;
            this.height += 2 * dy + 1;
            return this;
        },
        offset: function (dx, dy) {
            var x = dx, y = dy;
            if (dx instanceof Point) {
                x = dx.x;
                y = dx.y;
            }
            this.x += x;
            this.y += y;
            return this;
        },
        union: function (r) {
            var x1 = Math.min(this.x, r.x);
            var y1 = Math.min(this.y, r.y);
            var x2 = Math.max((this.x + this.width), (r.x + r.width));
            var y2 = Math.max((this.y + this.height), (r.y + r.height));
            return new Rect(x1, y1, x2 - x1, y2 - y1);
        },
        center: function () {
            return new Point(this.x + this.width / 2, this.y + this.height / 2);
        },
        top: function () {
            return new Point(this.x + this.width / 2, this.y);
        },
        right: function () {
            return new Point(this.x + this.width, this.y + this.height / 2);
        },
        bottom: function () {
            return new Point(this.x + this.width / 2, this.y + this.height);
        },
        left: function () {
            return new Point(this.x, this.y + this.height / 2);
        },
        topLeft: function () {
            return new Point(this.x, this.y);
        },
        topRight: function () {
            return new Point(this.x + this.width, this.y);
        },
        bottomLeft: function () {
            return new Point(this.x, this.y + this.height);
        },
        bottomRight: function () {
            return new Point(this.x + this.width, this.y + this.height);
        },
        clone: function () {
            return new Rect(this.x, this.y, this.width, this.height);
        },
        isEmpty: function () {
            return !this.width && !this.height;
        },
        equals: function (rect) {
            return this.x === rect.x && this.y === rect.y && this.width === rect.width && this.height === rect.height;
        },
        rotatedBounds: function (angle) {
            var rect = this.clone(),
                points = this.rotatedPoints(angle),
                tl = points[0],
                tr = points[1],
                br = points[2],
                bl = points[3];

            rect.x = Math.min(br.x, tl.x, tr.x, bl.x);
            rect.y = Math.min(br.y, tl.y, tr.y, bl.y);
            rect.width = Math.max(br.x, tl.x, tr.x, bl.x) - rect.x;
            rect.height = Math.max(br.y, tl.y, tr.y, bl.y) - rect.y;

            return rect;
        },
        rotatedPoints: function (angle) {
            var rect = this,
                c = rect.center(),
                br = rect.bottomRight().rotate(c, 360 - angle),
                tl = rect.topLeft().rotate(c, 360 - angle),
                tr = rect.topRight().rotate(c, 360 - angle),
                bl = rect.bottomLeft().rotate(c, 360 - angle);

            return [tl, tr, br, bl];
        },
        toString: function (delimiter) {
            delimiter = delimiter || " ";

            return this.x + delimiter + this.y + delimiter + this.width + delimiter + this.height;
        },
        scale: function (scaleX, scaleY, staicPoint, adornerCenter, angle) {
            var tl = this.topLeft();
            var thisCenter = this.center();
            tl.rotate(thisCenter, 360 - angle).rotate(adornerCenter, angle);

            var delta = staicPoint.minus(tl);
            var scaled = new Point(delta.x * scaleX, delta.y * scaleY);
            var position = delta.minus(scaled);
            tl = tl.plus(position);
            tl.rotate(adornerCenter, 360 - angle).rotate(thisCenter, angle);

            this.x = tl.x;
            this.y = tl.y;

            this.width *= scaleX;
            this.height *= scaleY;
        },

        zoom: function (zoom) {
            this.x *= zoom;
            this.y *= zoom;
            this.width *= zoom;
            this.height *= zoom;
            return this;
        }
    });

    var Size = Class.extend({
        init: function (width, height) {
            this.width = width;
            this.height = height;
        }
    });

    Size.prototype.Empty = new Size(0, 0);

    Rect.toRect = function (rect) {
        if (!(rect instanceof Rect)) {
            rect = new Rect(rect.x, rect.y, rect.width, rect.height);
        }

        return rect;
    };

    Rect.empty = function () {
        return new Rect(0, 0, 0, 0);
    };

    Rect.fromPoints = function (p, q) {
        if (isNaN(p.x) || isNaN(p.y) || isNaN(q.x) || isNaN(q.y)) {
            throw "Some values are NaN.";
        }
        return new Rect(Math.min(p.x, q.x), Math.min(p.y, q.y), Math.abs(p.x - q.x), Math.abs(p.y - q.y));
    };

    function isNearZero(num) {
        return Math.abs(num) < EPSILON;
    }

    function intersectLine(start1, end1, start2, end2, isSegment) {
        var tangensdiff = ((end1.x - start1.x) * (end2.y - start2.y)) - ((end1.y - start1.y) * (end2.x - start2.x));
        if (isNearZero(tangensdiff)) {
            //parallel lines
            return;
        }

        var num1 = ((start1.y - start2.y) * (end2.x - start2.x)) - ((start1.x - start2.x) * (end2.y - start2.y));
        var num2 = ((start1.y - start2.y) * (end1.x - start1.x)) - ((start1.x - start2.x) * (end1.y - start1.y));
        var r = num1 / tangensdiff;
        var s = num2 / tangensdiff;

        if (isSegment && (r < 0 || r > 1 || s < 0 || s > 1)) {
            //r < 0 => line 1 is below line 2
            //r > 1 => line 1 is above line 2
            //s < 0 => line 2 is below line 1
            //s > 1 => line 2 is above line 1
            return;
        }

        return new Point(start1.x + (r * (end1.x - start1.x)), start1.y + (r * (end1.y - start1.y)));
    }

    var Intersect = {
        lines: function (start1, end1, start2, end2) {
            return intersectLine(start1, end1, start2, end2);
        },
        segments: function (start1, end1, start2, end2) {
            return intersectLine(start1, end1, start2, end2, true);
        },
        rectWithLine: function (rect, start, end) {
            return Intersect.segments(start, end, rect.topLeft(), rect.topRight()) ||
                Intersect.segments(start, end, rect.topRight(), rect.bottomRight()) ||
                Intersect.segments(start, end, rect.bottomLeft(), rect.bottomRight()) ||
                Intersect.segments(start, end, rect.topLeft(), rect.bottomLeft());
        },
        rects: function (rect1, rect2, angle) {
            var tl = rect2.topLeft(),
                tr = rect2.topRight(),
                bl = rect2.bottomLeft(),
                br = rect2.bottomRight();
            var center = rect2.center();
            if (angle) {
                tl = tl.rotate(center, angle);
                tr = tr.rotate(center, angle);
                bl = bl.rotate(center, angle);
                br = br.rotate(center, angle);
            }

            var intersect = rect1.contains(tl) ||
                rect1.contains(tr) ||
                rect1.contains(bl) ||
                rect1.contains(br) ||
                Intersect.rectWithLine(rect1, tl, tr) ||
                Intersect.rectWithLine(rect1, tl, bl) ||
                Intersect.rectWithLine(rect1, tr, br) ||
                Intersect.rectWithLine(rect1, bl, br);

            if (!intersect) {//last possible case is rect1 to be completely within rect2
                tl = rect1.topLeft();
                tr = rect1.topRight();
                bl = rect1.bottomLeft();
                br = rect1.bottomRight();

                if (angle) {
                    var reverseAngle = 360 - angle;
                    tl = tl.rotate(center, reverseAngle);
                    tr = tr.rotate(center, reverseAngle);
                    bl = bl.rotate(center, reverseAngle);
                    br = br.rotate(center, reverseAngle);
                }

                intersect = rect2.contains(tl) ||
                    rect2.contains(tr) ||
                    rect2.contains(bl) ||
                    rect2.contains(br);
            }

            return intersect;
        }
    };

    /**
    * Aligns two rectangles, where one is the container and the other is content.
    */
    var RectAlign = Class.extend({
        init: function (container) {
            this.container = Rect.toRect(container);
        },

        align: function (content, alignment) {
            var alignValues = alignment.toLowerCase().split(" ");

            for (var i = 0; i < alignValues.length; i++) {
                content = this._singleAlign(content, alignValues[i]);
            }

            return content;
        },
        _singleAlign: function (content, alignment) {
            if (isFunction(this[alignment])) {
                return this[alignment](content);
            }
            else {
                return content;
            }
        },

        left: function (content) {
            return this._align(content, this._left);
        },
        center: function (content) {
            return this._align(content, this._center);
        },
        right: function (content) {
            return this._align(content, this._right);
        },
        stretch: function (content) {
            return this._align(content, this._stretch);
        },
        top: function (content) {
            return this._align(content, this._top);
        },
        middle: function (content) {
            return this._align(content, this._middle);
        },
        bottom: function (content) {
            return this._align(content, this._bottom);
        },

        _left: function (container, content) {
            content.x = container.x;
        },
        _center: function (container, content) {
            content.x = ((container.width - content.width) / 2) || 0;
        },
        _right: function (container, content) {
            content.x = container.width - content.width;
        },
        _top: function (container, content) {
            content.y = container.y;
        },
        _middle: function (container, content) {
            content.y = ((container.height - content.height) / 2) || 0;
        },
        _bottom: function (container, content) {
            content.y = container.height - content.height;
        },
        _stretch: function (container, content) {
            content.x = 0;
            content.y = 0;
            content.height = container.height;
            content.width = container.width;
        },
        _align: function (content, alignCalc) {
            content = Rect.toRect(content);
            alignCalc(this.container, content);

            return content;
        }
    });

    var Polar = Class.extend({
        init: function (r, a) {
            this.r = r;
            this.angle = a;
        }
    });

    /**
    * SVG transformation matrix.
    */
    var Matrix = Class.extend({
        init: function (a, b, c, d, e, f) {
            this.a = a || 0;
            this.b = b || 0;
            this.c = c || 0;
            this.d = d || 0;
            this.e = e || 0;
            this.f = f || 0;
        },
        plus: function (m) {
            this.a += m.a;
            this.b += m.b;
            this.c += m.c;
            this.d += m.d;
            this.e += m.e;
            this.f += m.f;
        },
        minus: function (m) {
            this.a -= m.a;
            this.b -= m.b;
            this.c -= m.c;
            this.d -= m.d;
            this.e -= m.e;
            this.f -= m.f;
        },
        times: function (m) {
            return new Matrix(
                this.a * m.a + this.c * m.b,
                this.b * m.a + this.d * m.b,
                this.a * m.c + this.c * m.d,
                this.b * m.c + this.d * m.d,
                this.a * m.e + this.c * m.f + this.e,
                this.b * m.e + this.d * m.f + this.f
            );
        },
        apply: function (p) {
            return new Point(this.a * p.x + this.c * p.y + this.e, this.b * p.x + this.d * p.y + this.f);
        },
        applyRect: function (r) {
            return Rect.fromPoints(this.apply(r.topLeft()), this.apply(r.bottomRight()));
        },
        toString: function () {
            return "matrix(" + this.a + " " + this.b + " " + this.c + " " + this.d + " " + this.e + " " + this.f + ")";
        }
    });

    deepExtend(Matrix, {
        fromSVGMatrix: function (vm) {
            var m = new Matrix();
            m.a = vm.a;
            m.b = vm.b;
            m.c = vm.c;
            m.d = vm.d;
            m.e = vm.e;
            m.f = vm.f;
            return m;
        },
        fromMatrixVector: function (v) {
            var m = new Matrix();
            m.a = v.a;
            m.b = v.b;
            m.c = v.c;
            m.d = v.d;
            m.e = v.e;
            m.f = v.f;
            return m;
        },
        fromList: function (v) {
            if (v.length !== 6) {
                throw "The given list should consist of six elements.";
            }
            var m = new Matrix();
            m.a = v[0];
            m.b = v[1];
            m.c = v[2];
            m.d = v[3];
            m.e = v[4];
            m.f = v[5];
            return m;
        },
        translation: function (x, y) {
            var m = new Matrix();
            m.a = 1;
            m.b = 0;
            m.c = 0;
            m.d = 1;
            m.e = x;
            m.f = y;
            return m;
        },
        unit: function () {
            return new Matrix(1, 0, 0, 1, 0, 0);
        },
        rotation: function (angle, x, y) {
            var m = new Matrix();
            m.a = Math.cos(angle * Math.PI / 180);
            m.b = Math.sin(angle * Math.PI / 180);
            m.c = -m.b;
            m.d = m.a;
            m.e = (x - x * m.a + y * m.b) || 0;
            m.f = (y - y * m.a - x * m.b) || 0;
            return m;
        },
        scaling: function (scaleX, scaleY) {
            var m = new Matrix();
            m.a = scaleX;
            m.b = 0;
            m.c = 0;
            m.d = scaleY;
            m.e = 0;
            m.f = 0;
            return m;
        },
        parse: function (v) {
            var parts, nums;
            if (v) {
                v = v.trim();
                // of the form "matrix(...)"
                if (v.slice(0, 6).toLowerCase() === "matrix") {
                    nums = v.slice(7, v.length - 1).trim();
                    parts = nums.split(",");
                    if (parts.length === 6) {
                        return Matrix.fromList(map(parts, function (p) {
                            return parseFloat(p);
                        }));
                    }
                    parts = nums.split(" ");
                    if (parts.length === 6) {
                        return Matrix.fromList(map(parts, function (p) {
                            return parseFloat(p);
                        }));
                    }
                }
                // of the form "(...)"
                if (v.slice(0, 1) === "(" && v.slice(v.length - 1) === ")") {
                    v = v.substr(1, v.length - 1);
                }
                if (v.indexOf(",") > 0) {
                    parts = v.split(",");
                    if (parts.length === 6) {
                        return Matrix.fromList(map(parts, function (p) {
                            return parseFloat(p);
                        }));
                    }
                }
                if (v.indexOf(" ") > 0) {
                    parts = v.split(" ");
                    if (parts.length === 6) {
                        return Matrix.fromList(map(parts, function (p) {
                            return parseFloat(p);
                        }));
                    }
                }
            }
            return parts;
        }
    });

    /**
    * SVG transformation represented as a vector.
    */
    var MatrixVector = Class.extend({
        init: function (a, b, c, d, e, f) {
            this.a = a || 0;
            this.b = b || 0;
            this.c = c || 0;
            this.d = d || 0;
            this.e = e || 0;
            this.f = f || 0;
        },
        fromMatrix: function FromMatrix(m) {
            var v = new MatrixVector();
            v.a = m.a;
            v.b = m.b;
            v.c = m.c;
            v.d = m.d;
            v.e = m.e;
            v.f = m.f;
            return v;
        }
    });

    /**
    * Returns a value with Gaussian (normal) distribution.
    * @param mean The mean value of the distribution.
    * @param deviation The deviation (spreading at half-height) of the distribution.
    * @returns {number}
    */
    function normalVariable(mean, deviation) {
        var x, y, r;
        do {
            x = Math.random() * 2 - 1;
            y = Math.random() * 2 - 1;
            r = x * x + y * y;
        }
        while (!r || r > 1);
        return mean + deviation * x * Math.sqrt(-2 * Math.log(r) / r);
    }

    /**
    * Returns a random identifier which can be used as an ID of objects, eventually augmented with a prefix.
    * @returns {string}
    */
    function randomId(length) {
        if (Utils.isUndefined(length)) {
            length = 10;
        }
        // old version return Math.floor((1 + Math.random()) * 0x1000000).toString(16).substring(1);
        var result = '';
        var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        for (var i = length; i > 0; --i) {
            result += chars.charAt(Math.round(Math.random() * (chars.length - 1)));
        }
        return result;
    }

    var Geometry = {

        /**
        * Returns the squared distance to the line defined by the two given Points.
        * @param p An arbitrary Point.
        * @param a An endpoint of the line or segment.
        * @param b The complementary endpoint of the line or segment.
        */
        _distanceToLineSquared: function (p, a, b) {
            function d2(pt1, pt2) {
                return (pt1.x - pt2.x) * (pt1.x - pt2.x) + (pt1.y - pt2.y) * (pt1.y - pt2.y);
            }

            if (a === b) { // returns the distance of p to a
                return d2(p, a);
            }

            var vx = b.x - a.x,
                vy = b.y - a.y,
                dot = (p.x - a.x) * vx + (p.y - a.y) * vy;
            if (dot < 0) {
                return d2(a, p); // sits on side of a
            }

            dot = (b.x - p.x) * vx + (b.y - p.y) * vy;
            if (dot < 0) {
                return d2(b, p); // sits on side of b
            }
            // regular case, use crossproduct to get the sine out
            dot = (b.x - p.x) * vy - (b.y - p.y) * vx;
            return dot * dot / (vx * vx + vy * vy);
        },

        /**
        * Returns the distance to the line defined by the two given Points.
        * @param p An arbitrary Point.
        * @param a An endpoint of the line or segment.
        * @param b The complementary endpoint of the line or segment.
        */
        distanceToLine: function (p, a, b) {
            return Math.sqrt(this._distanceToLineSquared(p, a, b));
        },

        /**
        * Returns the distance of the given points to the polyline defined by the points.
        * @param p An arbitrary point.
        * @param points The points defining the polyline.
        * @returns {Number}
        */
        distanceToPolyline: function (p, points) {
            var minimum = Number.MAX_VALUE;
            if (Utils.isUndefined(points) || points.length === 0) {
                return Number.MAX_VALUE;
            }
            for (var s = 0; s < points.length - 1; s++) {
                var p1 = points[s];
                var p2 = points[s + 1];

                var d = this._distanceToLineSquared(p, p1, p2);
                if (d < minimum) {
                    minimum = d;
                }
            }
            return Math.sqrt(minimum);
        }
    };

    /*---------------The HashTable structure--------------------------------*/

    /**
    * Represents a collection of key-value pairs that are organized based on the hash code of the key.
    * _buckets[hashId] = {key: key, value:...}
    * Important: do not use the standard Array access method, use the get/set methods instead.
    * See http://en.wikipedia.org/wiki/Hash_table
    */
    var HashTable = kendo.Class.extend({
        init: function () {
            this._buckets = [];
            this.length = 0;
        },

        /**
        * Adds the literal object with the given key (of the form {key: key,....}).
        */
        add: function (key, value) {

            var obj = this._createGetBucket(key);
            if (Utils.isDefined(value)) {
                obj.value = value;
            }
            return obj;
        },

        /**
        * Gets the literal object with the given key.
        */
        get: function (key) {
            if (this._bucketExists(key)) {
                return this._createGetBucket(key);
            }
            return null;
        },

        /**
        * Set the key-value pair.
        * @param key The key of the entry.
        * @param value The value to set. If the key already exists the value will be overwritten.
        */
        set: function (key, value) {
            this.add(key, value);
        },

        /**
        * Determines whether the HashTable contains a specific key.
        */
        containsKey: function (key) {
            return this._bucketExists(key);
        },

        /**
        * Removes the element with the specified key from the hashtable.
        * Returns the removed bucket.
        */
        remove: function (key) {
            if (this._bucketExists(key)) {
                var hashId = this._hash(key);
                delete this._buckets[hashId];
                this.length--;
                return key;
            }
        },

        /**
        * Foreach with an iterator working on the key-value pairs.
        * @param func
        */
        forEach: function (func) {
            var hashes = this._hashes();
            for (var i = 0, len = hashes.length; i < len; i++) {
                var hash = hashes[i];
                var bucket = this._buckets[hash];
                if (Utils.isUndefined(bucket)) {
                    continue;
                }
                func(bucket);
            }
        },

        /**
        * Returns a (shallow) clone of the current HashTable.
        * @returns {HashTable}
        */
        clone: function () {
            var ht = new HashTable();
            var hashes = this._hashes();
            for (var i = 0, len = hashes.length; i < len; i++) {
                var hash = hashes[i];
                var bucket = this._buckets[hash];
                if (Utils.isUndefined(bucket)) {
                    continue;
                }
                ht.add(bucket.key, bucket.value);
            }
            return ht;
        },

        /**
        * Returns the hashes of the buckets.
        * @returns {Array}
        * @private
        */
        _hashes: function () {
            var hashes = [];
            for (var hash in this._buckets) {
                if (this._buckets.hasOwnProperty(hash)) {
                    hashes.push(hash);
                }
            }
            return hashes;
        },

        _bucketExists: function (key) {
            var hashId = this._hash(key);
            return Utils.isDefined(this._buckets[hashId]);
        },

        /**
        * Returns-adds the createGetBucket with the given key. If not present it will
        * be created and returned.
        * A createGetBucket is a literal object of the form {key: key, ...}.
        */
        _createGetBucket: function (key) {
            var hashId = this._hash(key);
            var bucket = this._buckets[hashId];
            if (Utils.isUndefined(bucket)) {
                bucket = { key: key };
                this._buckets[hashId] = bucket;
                this.length++;
            }
            return bucket;
        },

        /**
        * Hashing of the given key.
        */
        _hash: function (key) {
            if (Utils.isNumber(key)) {
                return key;
            }
            if (Utils.isString(key)) {
                return this._hashString(key);
            }
            if (Utils.isObject(key)) {
                return this._objectHashId(key);
            }
            throw "Unsupported key type.";
        },

        /**
        * Hashing of a string.
        */
        _hashString: function (s) {
            // see for example http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
            var result = 0;
            if (s.length === 0) {
                return result;
            }
            for (var i = 0; i < s.length; i++) {
                var ch = s.charCodeAt(i);
                result = ((result * 32) - result) + ch;
            }
            return result;
        },

        /**
        * Returns the unique identifier for an object. This is automatically assigned and add on the object.
        */
        _objectHashId: function (key) {
            var id = key._hashId;
            if (Utils.isUndefined(id)) {
                id = randomId();
                key._hashId = id;
            }
            return id;
        }
    });

    /*---------------The Dictionary structure--------------------------------*/

    /**
    * Represents a collection of key-value pairs.
    * Important: do not use the standard Array access method, use the get/Set methods instead.
    */
    var Dictionary = kendo.Observable.extend({
        /**
        * Initializes a new instance of the Dictionary class.
        * @param dictionary Loads the content of the given dictionary into this new one.
        */
        init: function (dictionary) {
            var that = this;
            kendo.Observable.fn.init.call(that);
            this._hashTable = new HashTable();
            this.length = 0;
            if (Utils.isDefined(dictionary)) {
                if ($.isArray(dictionary)) {
                    for (var i = 0; i < dictionary.length; i++) {
                        this.add(dictionary[i]);
                    }
                } else {
                    dictionary.forEach(function (k, v) {
                        this.add(k, v);
                    }, this);
                }
            }
        },

        /**
        * Adds a key-value to the dictionary.
        * If the key already exists this will assign the given value to the existing entry.
        */
        add: function (key, value) {
            var entry = this._hashTable.get(key);
            if (!entry) {
                entry = this._hashTable.add(key);
                this.length++;
                this.trigger('changed');
            }
            entry.value = value;
        },

        /**
        * Set the key-value pair.
        * @param key The key of the entry.
        * @param value The value to set. If the key already exists the value will be overwritten.
        */
        set: function (key, value) {
            this.add(key, value);
        },

        /**
        * Gets the value associated with the given key in the dictionary.
        */
        get: function (key) {
            var entry = this._hashTable.get(key);
            if (entry) {
                return entry.value;
            }
            throw new Error("Cannot find key " + key);
        },

        /**
        * Returns whether the dictionary contains the given key.
        */
        containsKey: function (key) {
            return this._hashTable.containsKey(key);
        },

        /**
        * Removes the element with the specified key from the dictionary.
        */
        remove: function (key) {
            if (this.containsKey(key)) {
                this.trigger("changed");
                this.length--;
                return this._hashTable.remove(key);
            }
        },

        /**
        * The functional gets the key and value as parameters.
        */
        forEach: function (func, thisRef) {
            this._hashTable.forEach(function (entry) {
                func.call(thisRef, entry.key, entry.value);
            });
        },

        /**
        * Same as forEach except that only the value is passed to the functional.
        */
        forEachValue: function (func, thisRef) {
            this._hashTable.forEach(function (entry) {
                func.call(thisRef, entry.value);
            });
        },

        /**
        * Calls a defined callback function for each key in the dictionary.
        */
        forEachKey: function (func, thisRef) {
            this._hashTable.forEach(function (entry) {
                func.call(thisRef, entry.key);
            });
        },

        /**
        * Gets an array with all keys in the dictionary.
        */
        keys: function () {
            var keys = [];
            this.forEachKey(function (key) {
                keys.push(key);
            });
            return keys;
        }
    });

    /*---------------Queue structure--------------------------------*/

    var Queue = kendo.Class.extend({

        init: function () {
            this._tail = null;
            this._head = null;
            this.length = 0;
        },

        /**
        * Enqueues an object to the end of the queue.
        */
        enqueue: function (value) {
            var entry = { value: value, next: null };
            if (!this._head) {
                this._head = entry;
                this._tail = this._head;
            }
            else {
                this._tail.next = entry;
                this._tail = this._tail.next;
            }
            this.length++;
        },

        /**
        * Removes and returns the object at top of the queue.
        */
        dequeue: function () {
            if (this.length < 1) {
                throw new Error("The queue is empty.");
            }
            var value = this._head.value;
            this._head = this._head.next;
            this.length--;
            return value;
        },

        contains: function (item) {
            var current = this._head;
            while (current) {
                if (current.value === item) {
                    return true;
                }
                current = current.next;
            }
            return false;
        }
    });


    /**
    * While other data structures can have multiple times the same item a Set owns only
    * once a particular item.
    * @type {*}
    */
    var Set = kendo.Observable.extend({
        init: function (resource) {
            var that = this;
            kendo.Observable.fn.init.call(that);
            this._hashTable = new HashTable();
            this.length = 0;
            if (Utils.isDefined(resource)) {
                if (resource instanceof HashTable) {
                    resource.forEach(function (d) {
                        this.add(d);
                    });
                }
                else if (resource instanceof Dictionary) {
                    resource.forEach(function (k, v) {
                        this.add({ key: k, value: v });
                    }, this);
                }
            }
        },

        contains: function (item) {
            return this._hashTable.containsKey(item);
        },

        add: function (item) {
            var entry = this._hashTable.get(item);
            if (!entry) {
                this._hashTable.add(item, item);
                this.length++;
                this.trigger('changed');
            }
        },

        get: function (item) {
            if (this.contains(item)) {
                return this._hashTable.get(item).value;
            }
            else {
                return null;
            }
        },

        /**
        * Returns the hash of the item.
        * @param item
        * @returns {*}
        */
        hash: function (item) {
            return this._hashTable._hash(item);
        },

        /**
        * Removes the given item from the set. No exception is thrown if the item is not in the Set.
        * @param item
        */
        remove: function (item) {
            if (this.contains(item)) {
                this._hashTable.remove(item);
                this.length--;
                this.trigger('changed');
            }
        },
        /**
        * Foreach with an iterator working on the key-value pairs.
        * @param func
        */
        forEach: function (func, context) {
            this._hashTable.forEach(function (kv) {
                func(kv.value);
            }, context);
        },
        toArray: function () {
            var r = [];
            this.forEach(function (d) {
                r.push(d);
            });
            return r;
        }
    });

    /*----------------Node-------------------------------*/

    /**
    * Defines the node (vertex) of a Graph.
    */
    var Node = kendo.Class.extend({

        init: function (id, shape) {

            /**
            * Holds all the links incident with the current node.
            * Do not use this property to manage the incoming links, use the appropriate add/remove methods instead.
            */
            this.links = [];

            /**
            * Holds the links from the current one to another Node .
            * Do not use this property to manage the incoming links, use the appropriate add/remove methods instead.
            */
            this.outgoing = [];

            /**
            * Holds the links from another Node to the current one.
            * Do not use this property to manage the incoming links, use the appropriate add/remove methods instead.
            */
            this.incoming = [];

            /**
            * Holds the weight of this Node.
            */
            this.weight = 1;

            if (Utils.isDefined(id)) {
                this.id = id;
            }
            else {
                this.id = randomId();
            }
            if (Utils.isDefined(shape)) {
                this.associatedShape = shape;
                // transfer the shape's bounds to the runtime props
                var b = shape.bounds();
                this.width = b.width;
                this.height = b.height;
                this.x = b.x;
                this.y = b.y;
            }
            else {
                this.associatedShape = null;
            }
            /**
            * The payload of the node.
            * @type {null}
            */
            this.data = null;
            this.type = "Node";
            this.shortForm = "Node '" + this.id + "'";
            /**
            * Whether this is an injected node during the analysis or layout process.
            * @type {boolean}
            */
            this.isVirtual = false;
        },

        /**
        * Returns whether this node has no links attached.
        */
        isIsolated: function () {
            return Utils.isEmpty(this.links);
        },

        /**
        * Gets or sets the bounding rectangle of this node.
        * This should be considered as runtime data, the property is not hotlinked to a SVG item.
        */
        bounds: function (r) {
            if (!Utils.isDefined(r)) {
                return new diagram.Rect(this.x, this.y, this.width, this.height);
            }

            this.x = r.x;
            this.y = r.y;
            this.width = r.width;
            this.height = r.height;
        },

        /**
        * Returns whether there is at least one link with the given (complementary) node. This can be either an
        * incoming or outgoing link.
        */
        isLinkedTo: function (node) {
            var that = this;
            return Utils.any(that.links, function (link) {
                return link.getComplement(that) === node;
            });
        },

        /**
        * Gets the children of this node, defined as the adjacent nodes with a link from this node to the adjacent one.
        * @returns {Array}
        */
        getChildren: function () {
            if (this.outgoing.length === 0) {
                return [];
            }
            var children = [];
            for (var i = 0, len = this.outgoing.length; i < len; i++) {
                var link = this.outgoing[i];
                children.push(link.getComplement(this));
            }
            return children;
        },

        /**
        * Gets the parents of this node, defined as the adjacent nodes with a link from the adjacent node to this one.
        * @returns {Array}
        */
        getParents: function () {
            if (this.incoming.length === 0) {
                return [];
            }
            var parents = [];
            for (var i = 0, len = this.incoming.length; i < len; i++) {
                var link = this.incoming[i];
                parents.push(link.getComplement(this));
            }
            return parents;
        },

        /**
        * Returns a clone of the Node. Note that the identifier is not cloned since it's a different Node instance.
        * @returns {Node}
        */
        clone: function () {
            var copy = new Node();
            if (Utils.isDefined(this.weight)) {
                copy.weight = this.weight;
            }
            if (Utils.isDefined(this.balance)) {
                copy.balance = this.balance;
            }
            if (Utils.isDefined(this.owner)) {
                copy.owner = this.owner;
            }
            copy.associatedShape = this.associatedShape;
            copy.x = this.x;
            copy.y = this.y;
            copy.width = this.width;
            copy.height = this.height;
            return copy;
        },

        /**
        * Returns whether there is a link from the current node to the given node.
        */
        adjacentTo: function (node) {
            return this.isLinkedTo(node) !== null;
        },

        /**
        * Removes the given link from the link collection this node owns.
        * @param link
        */
        removeLink: function (link) {
            if (link.source === this) {
                Utils.remove(this.links, link);
                Utils.remove(this.outgoing, link);
                link.source = null;
            }

            if (link.target === this) {
                Utils.remove(this.links, link);
                Utils.remove(this.incoming, link);
                link.target = null;
            }
        },

        /**
        * Returns whether there is a (outgoing) link from the current node to the given one.
        */
        hasLinkTo: function (node) {
            return Utils.any(this.outgoing, function (link) {
                return link.target === node;
            });
        },

        /**
        * Returns the degree of this node, i.e. the sum of incoming and outgoing links.
        */
        degree: function () {
            return this.links.length;
        },

        /**
        * Returns whether this node is either the source or the target of the given link.
        */
        incidentWith: function (link) {
            return contains(this.links, link);
        },

        /**
        * Returns the links between this node and the given one.
        */
        getLinksWith: function (node) {
            return Utils.all(this.links, function (link) {
                return link.getComplement(this) === node;
            }, this);
        },

        /**
        * Returns the nodes (either parent or child) which are linked to the current one.
        */
        getNeighbors: function () {
            var neighbors = [];
            Utils.forEach(this.incoming, function (e) {
                neighbors.push(e.getComplement(this));
            }, this);
            Utils.forEach(this.outgoing, function (e) {
                neighbors.push(e.getComplement(this));
            }, this);
            return neighbors;
        }
    });

    /**
    * Defines a directed link (edge, connection) of a Graph.
    */
    var Link = kendo.Class.extend({

        init: function (source, target, id, connection) {
            if (Utils.isUndefined(source)) {
                throw "The source of the new link is not set.";
            }
            if (Utils.isUndefined(target)) {
                throw "The target of the new link is not set.";
            }
            var sourceFound, targetFound;
            if (Utils.isString(source)) {
                sourceFound = new Node(source);
            }
            else {
                sourceFound = source;
            }
            if (Utils.isString(target)) {
                targetFound = new Node(target);
            }
            else {
                targetFound = target;
            }

            this.source = sourceFound;
            this.target = targetFound;
            this.source.links.push(this);
            this.target.links.push(this);
            this.source.outgoing.push(this);
            this.target.incoming.push(this);
            if (Utils.isDefined(id)) {
                this.id = id;
            }
            else {
                this.id = randomId();
            }
            if (Utils.isDefined(connection)) {
                this.associatedConnection = connection;
            }
            else {
                this.associatedConnection = null;
            }
            this.type = "Link";
            this.shortForm = "Link '" + this.source.id + "->" + this.target.id + "'";
        },

        /**
        * Returns the complementary node of the given one, if any.
        */
        getComplement: function (node) {
            if (this.source !== node && this.target !== node) {
                throw "The given node is not incident with this link.";
            }
            return this.source === node ? this.target : this.source;
        },

        /**
        * Returns the overlap of the current link with the given one, if any.
        */
        getCommonNode: function (link) {
            if (this.source === link.source || this.source === link.target) {
                return this.source;
            }
            if (this.target === link.source || this.target === link.target) {
                return this.target;
            }
            return null;
        },

        /**
        * Returns whether the current link is bridging the given nodes.
        */
        isBridging: function (v1, v2) {
            return this.source === v1 && this.target === v2 || this.source === v2 && this.target === v1;
        },

        /**
        * Returns the source and target of this link as a tuple.
        */
        getNodes: function () {
            return [this.source, this.target];
        },

        /**
        * Returns whether the given node is either the source or the target of the current link.
        */
        incidentWith: function (node) {
            return this.source === node || this.target === node;
        },

        /**
        * Returns whether the given link is a continuation of the current one. This can be both
        * via an incoming or outgoing link.
        */
        adjacentTo: function (link) {
            return contains(this.source.links, link) || contains(this.target.links, link);
        },

        /**
        * Changes the source-node of this link.
        */
        changeSource: function (node) {
            Utils.remove(this.source.links, this);
            Utils.remove(this.source.outgoing, this);

            node.links.push(this);
            node.outgoing.push(this);

            this.source = node;
        },

        /**
        * Changes the target-node of this link.
        * @param node
        */
        changeTarget: function (node) {
            Utils.remove(this.target.links, this);
            Utils.remove(this.target.incoming, this);

            node.links.push(this);
            node.incoming.push(this);

            this.target = node;
        },

        /**
        * Changes both the source and the target nodes of this link.
        */
        changesNodes: function (v, w) {
            if (this.source === v) {
                this.changeSource(w);
            }
            else if (this.target === v) {
                this.changeTarget(w);
            }
        },

        /**
        * Reverses the direction of this link.
        */
        reverse: function () {
            var oldSource = this.source;
            var oldTarget = this.target;

            this.source = oldTarget;
            Utils.remove(oldSource.outgoing, this);
            this.source.outgoing.push(this);

            this.target = oldSource;
            Utils.remove(oldTarget.incoming, this);
            this.target.incoming.push(this);
            return this;
        },

        /**
        * Ensures that the given target defines the endpoint of this link.
        */
        directTo: function (target) {
            if (this.source !== target && this.target !== target) {
                throw "The given node is not incident with this link.";
            }
            if (this.target !== target) {
                this.reverse();
            }
        },

        /**
        * Returns a reversed clone of this link.
        */
        createReverseEdge: function () {
            var r = this.clone();
            r.reverse();
            r.reversed = true;
            return r;
        },

        /**
        * Returns a clone of this link.
        */
        clone: function () {
            var clone = new Link(this.source, this.target);
            return clone;
        }
    });

    /*--------------Graph structure---------------------------------*/
    /**
    * Defines a directed graph structure.
    * Note that the incidence structure resides in the nodes through the incoming and outgoing links collection, rahter than
    * inside the Graph.
    */
    var Graph = kendo.Class.extend({
        init: function (idOrDiagram) {
            /**
            * The links or edge collection of this Graph.
            * @type {Array}
            */
            this.links = [];
            /**
            * The node or vertex collection of this Graph.
            * @type {Array}
            */
            this.nodes = [];
            /**
            * The optional reference to the Diagram on which this Graph is based.
            * @type {null}
            */
            this.diagram = null;

            /**
            * The root of this Graph. If not set explicitly the first Node with zero incoming links will be taken.
            * @type {null}
            * @private
            */
            this._root = null;
            if (Utils.isDefined(idOrDiagram)) {
                if (Utils.isString(idOrDiagram)) {
                    this.id = idOrDiagram;
                }
                else {
                    this.diagram = idOrDiagram;
                    this.id = idOrDiagram.id;
                }
            }
            else {
                this.id = randomId();
            }

            /**
            * The bounds of this graph if the nodes have spatial extension defined.
            * @type {Rect}
            */
            this.bounds = new Rect();
            // keeps track whether the children & parents have been created
            this._hasCachedRelationships = false;
            this.type = "Graph";
        },
        /**
        * Caches the relational information of parents and children in the 'parents' and 'children'
        * properties.
        * @param forceRebuild If set to true the relational info will be rebuild even if already present.
        */
        cacheRelationships: function (forceRebuild) {
            if (Utils.isUndefined(forceRebuild)) {
                forceRebuild = false;
            }
            if (this._hasCachedRelationships && !forceRebuild) {
                return;
            }
            for (var i = 0, len = this.nodes.length; i < len; i++) {
                var node = this.nodes[i];
                node.children = this.getChildren(node);
                node.parents = this.getParents(node);
            }
            this._hasCachedRelationships = true;
        },

        /**
        * Assigns tree-levels to the nodes assuming this is a tree graph.
        * If not connected or not a tree the process will succeed but
        * will have little meaning.
        * @param startNode The node from where the level numbering starts, usually the root of the tree.
        * @param visited The collection of visited nodes.
        * @param offset The offset or starting counter of the level info.
        */
        assignLevels: function (startNode, offset, visited) {
            if (!startNode) {
                throw "Start node not specified.";
            }
            if (Utils.isUndefined(offset)) {
                offset = 0;
            }
            // if not done before, cache the parents and children
            this.cacheRelationships();
            if (Utils.isUndefined(visited)) {
                visited = new Dictionary();
                Utils.forEach(this.nodes, function (n) {
                    visited.add(n, false);
                });
            }
            visited.set(startNode, true);
            startNode.level = offset;
            var children = startNode.children;
            for (var i = 0, len = children.length; i < len; i++) {
                var child = children[i];
                if (!child || visited.get(child)) {
                    continue;
                }
                this.assignLevels(child, offset + 1, visited);
            }
        },

        /**
        * Gets or set the root of this graph.
        * If not set explicitly the first Node with zero incoming links will be taken.
        * @param value
        * @returns {*}
        */
        root: function (value) {
            if (Utils.isUndefined(value)) {
                if (!this._root) {
                    // TODO: better to use the longest path for the most probable root?
                    var found = Utils.first(this.nodes, function (n) {
                        return n.incoming.length === 0;
                    });
                    if (found) {
                        return found;
                    }
                    return Utils.first(this.nodes);
                }
                else {
                    return this._root;
                }
            }
            else {
                this._root = value;
            }
        },

        /**
        * Returns the connected components of this graph.
        * Note that the returned graphs are made up of the nodes and links of this graph, i.e. a pointer to the items of this graph.
        * If you alter the items of the components you'll alter the original graph and vice versa.
        * @returns {Array}
        */
        getConnectedComponents: function () {
            this.componentIndex = 0;
            this.setItemIndices();
            var componentId = Utils.initArray(this.nodes.length, -1);

            for (var v = 0; v < this.nodes.length; v++) {
                if (componentId[v] === -1) {
                    this._collectConnectedNodes(componentId, v);
                    this.componentIndex++;
                }
            }

            var components = [], i;
            for (i = 0; i < this.componentIndex; ++i) {
                components[i] = new Graph();
            }
            for (i = 0; i < componentId.length; ++i) {
                var graph = components[componentId[i]];
                graph.addNodeAndOutgoings(this.nodes[i]);
            }
            // sorting the components in decreasing order of node count
            components.sort(function (a, b) {
                return b.nodes.length - a.nodes.length;
            });
            return components;
        },

        _collectConnectedNodes: function (setIds, nodeIndex) {
            setIds[nodeIndex] = this.componentIndex; // part of the current component
            var node = this.nodes[nodeIndex];
            Utils.forEach(node.links,
                function (link) {
                    var next = link.getComplement(node);
                    var nextId = next.index;
                    if (setIds[nextId] === -1) {
                        this._collectConnectedNodes(setIds, nextId);
                    }
                }, this);
        },

        /**
        * Calculates the bounds of this Graph if the Nodes have spatial dimensions defined.
        * @returns {Rect}
        */
        calcBounds: function () {
            if (this.isEmpty()) {
                this.bounds = new Rect();
                return this.bounds;
            }
            var b = null;
            for (var i = 0, len = this.nodes.length; i < len; i++) {
                var node = this.nodes[i];
                if (!b) {
                    b = node.bounds();
                }
                else {
                    b = b.union(node.bounds());
                }
            }
            this.bounds = b;
            return this.bounds;
        },

        /**
        * Creates a spanning tree for the current graph.
        * Important: this will not return a spanning forest if the graph is disconnected.
        * Prim's algorithm  finds a minimum-cost spanning tree of an edge-weighted, connected, undirected graph;
        * see http://en.wikipedia.org/wiki/Prim%27s_algorithm .
        * @param root The root of the spanning tree.
        * @returns {Graph}
        */
        getSpanningTree: function (root) {
            var tree = new Graph();
            var map = new Dictionary(), source, target;
            tree.root = root.clone();
            tree.root.level = 0;
            tree.root.id = root.id;
            map.add(root, tree.root);
            root.level = 0;

            var visited = [];
            var remaining = [];
            tree.nodes.push(tree.root);
            visited.push(root);
            remaining.push(root);

            var levelCount = 1;
            while (remaining.length > 0) {
                var next = remaining.pop();
                for (var ni = 0; ni < next.links.length; ni++) {
                    var link = next.links[ni];
                    var cn = link.getComplement(next);
                    if (contains(visited, cn)) {
                        continue;
                    }

                    cn.level = next.level + 1;
                    if (levelCount < cn.level + 1) {
                        levelCount = cn.level + 1;
                    }
                    if (!contains(remaining, cn)) {
                        remaining.push(cn);
                    }
                    if (!contains(visited, cn)) {
                        visited.push(cn);
                    }
                    if (map.containsKey(next)) {
                        source = map.get(next);
                    }
                    else {
                        source = next.clone();
                        source.level = next.level;
                        source.id = next.id;
                        map.add(next, source);
                    }
                    if (map.containsKey(cn)) {
                        target = map.get(cn);
                    }
                    else {
                        target = cn.clone();
                        target.level = cn.level;
                        target.id = cn.id;
                        map.add(cn, target);
                    }
                    var newLink = new Link(source, target);
                    tree.addLink(newLink);
                }

            }

            var treeLevels = [];
            for (var i = 0; i < levelCount; i++) {
                treeLevels.push([]);
            }

            Utils.forEach(tree.nodes, function (node) {
                treeLevels[node.level].push(node);
            });

            tree.treeLevels = treeLevels;
            tree.cacheRelationships();
            return tree;
        },

        /**
        * Returns a random node in this graph.
        * @param excludedNodes The collection of nodes which should not be considered.
        * @param incidenceLessThan The maximum degree or incidence the random node should have.
        * @returns {*}
        */
        takeRandomNode: function (excludedNodes, incidenceLessThan) {
            if (Utils.isUndefined(excludedNodes)) {
                excludedNodes = [];
            }
            if (Utils.isUndefined(incidenceLessThan)) {
                incidenceLessThan = 4;
            }
            if (this.nodes.length === 0) {
                return null;
            }
            if (this.nodes.length === 1) {
                return contains(excludedNodes, this.nodes[0]) ? null : this.nodes[0];
            }
            var pool = $.grep(this.nodes, function (node) {
                return !contains(excludedNodes, node) && node.degree() <= incidenceLessThan;
            });
            if (Utils.isEmpty(pool)) {
                return null;
            }
            return pool[Utils.randomInteger(0, pool.length)];
        },

        /**
        * Returns whether this is an empty graph.
        */
        isEmpty: function () {
            return Utils.isEmpty(this.nodes);
        },

        /**
        * Checks whether the endpoints of the links are all in the nodes collection.
        */
        isHealthy: function () {
            return Utils.all(this.links, function (link) {
                return contains(this.nodes, link.source) && contains(this.nodes, link.target);
            }, this);
        },

        /**
        * Gets the parents of this node, defined as the adjacent nodes with a link from the adjacent node to this one.
        * @returns {Array}
        */
        getParents: function (n) {
            if (!this.hasNode(n)) {
                throw "The given node is not part of this graph.";
            }
            return n.getParents();
        },

        /**
        * Gets the children of this node, defined as the adjacent nodes with a link from this node to the adjacent one.
        * @returns {Array}
        */
        getChildren: function (n) {
            if (!this.hasNode(n)) {
                throw "The given node is not part of this graph.";
            }
            return n.getChildren();
        },

        /**
        * Adds a new link to the graph between the given nodes.
        */
        addLink: function (sourceOrLink, target, owner) {

            if (Utils.isUndefined(sourceOrLink)) {
                throw "The source of the link is not defined.";
            }
            if (Utils.isUndefined(target)) {
                // can only be undefined if the first one is a Link
                if (Utils.isDefined(sourceOrLink.type) && sourceOrLink.type === "Link") {
                    this.addExistingLink(sourceOrLink);
                    return;
                }
                else {
                    throw "The target of the link is not defined.";
                }
            }

            var foundSource = this.getNode(sourceOrLink);
            if (Utils.isUndefined(foundSource)) {
                foundSource = this.addNode(sourceOrLink);
            }
            var foundTarget = this.getNode(target);
            if (Utils.isUndefined(foundTarget)) {
                foundTarget = this.addNode(target);
            }

            var newLink = new Link(foundSource, foundTarget);

            if (Utils.isDefined(owner)) {
                newLink.owner = owner;
            }

            /*newLink.source.outgoing.push(newLink);
            newLink.source.links.push(newLink);
            newLink.target.incoming.push(newLink);
            newLink.target.links.push(newLink);*/

            this.links.push(newLink);

            return newLink;
        },

        /**
        * Removes all the links in this graph.
        */
        removeAllLinks: function () {
            while (this.links.length > 0) {
                var link = this.links[0];
                this.removeLink(link);
            }
        },

        /**
        * Adds the given link to the current graph.
        */
        addExistingLink: function (link) {

            if (this.hasLink(link)) {
                return;
            }
            this.links.push(link);
            if (this.hasNode(link.source.id)) {
                // priority to the existing node with the id even if other props are different
                var s = this.getNode(link.source.id);
                link.changeSource(s);
            }
            else {
                this.addNode(link.source);
            }

            if (this.hasNode(link.target.id)) {
                var t = this.getNode(link.target.id);
                link.changeTarget(t);
            }
            else {
                this.addNode(link.target);
            }

            /*  if (!link.source.outgoing.contains(link)) {
            link.source.outgoing.push(link);
            }
            if (!link.source.links.contains(link)) {
            link.source.links.push(link);
            }
            if (!link.target.incoming.contains(link)) {
            link.target.incoming.push(link);
            }
            if (!link.target.links.contains(link)) {
            link.target.links.push(link);
            }*/
        },

        /**
        * Returns whether the given identifier or Link is part of this graph.
        * @param linkOrId An identifier or a Link object.
        * @returns {*}
        */
        hasLink: function (linkOrId) {
            if (Utils.isString(linkOrId)) {
                return Utils.any(this.links, function (link) {
                    return link.id === linkOrId;
                });
            }
            if (linkOrId.type === "Link") {
                return contains(this.links, linkOrId);
            }
            throw "The given object is neither an identifier nor a Link.";
        },
        /**
        * Gets the node with the specified Id or null if not part of this graph.
        */
        getNode: function (nodeOrId) {
            if (Utils.isUndefined(nodeOrId)) {
                throw "No identifier or Node specified.";
            }
            if (Utils.isString(nodeOrId)) {
                return Utils.find(this.nodes, function (n) {
                    return n.id == nodeOrId;
                });
            }
            else {
                if (this.hasNode(nodeOrId)) {
                    return nodeOrId;
                }
                else {
                    return null;
                }
            }
        },

        /**
        * Returns whether the given node or node Id is part of this graph.
        */
        hasNode: function (nodeOrId) {
            if (Utils.isString(nodeOrId)) {
                return Utils.any(this.nodes, function (n) {
                    return n.id === nodeOrId;
                });
            }
            if (Utils.isObject(nodeOrId)) {
                return Utils.any(this.nodes, function (n) {
                    return n === nodeOrId;
                });
            }
            throw "The identifier should be a Node or the Id (string) of a node.";
        },

        /**
        * Removes the given node from this graph.
        * The node can be specified as an object or as an identifier (string).
        */
        removeNode: function (nodeOrId) {
            var n = nodeOrId;
            if (Utils.isString(nodeOrId)) {
                n = this.getNode(nodeOrId);
            }

            if (Utils.isDefined(n)) {
                var links = n.links;
                n.links = [];
                for (var i = 0, len = links.length; i < len; i++) {
                    var link = links[i];
                    this.removeLink(link);
                }
                Utils.remove(this.nodes, n);
            }
            else {
                throw "The identifier should be a Node or the Id (string) of a node.";
            }
        },

        /**
        * Returns whether the given nodes are connected with a least one link independently of the direction.
        */
        areConnected: function (n1, n2) {
            return Utils.any(this.links, function (link) {
                return link.source == n1 && link.target == n2 || link.source == n2 && link.target == n1;
            });
        },

        /**
        * Removes the given link from this graph.
        */
        removeLink: function (link) {
            /*    if (!this.links.contains(link)) {
            throw "The given link is not part of the Graph.";
            }
            */
            Utils.remove(this.links, link);

            Utils.remove(link.source.outgoing, link);
            Utils.remove(link.source.links, link);
            Utils.remove(link.target.incoming, link);
            Utils.remove(link.target.links, link);
        },

        /**
        * Adds a new node to this graph, if not already present.
        * The node can be an existing Node or the identifier of a new node.
        * No error is thrown if the node is already there and the existing one is returned.
        */
        addNode: function (nodeOrId, layoutRect, owner) {

            var newNode = null;

            if (!Utils.isDefined(nodeOrId)) {
                throw "No Node or identifier for a new Node is given.";
            }

            if (Utils.isString(nodeOrId)) {
                if (this.hasNode(nodeOrId)) {
                    return this.getNode(nodeOrId);
                }
                newNode = new Node(nodeOrId);
            }
            else {
                if (this.hasNode(nodeOrId)) {
                    return this.getNode(nodeOrId);
                }
                // todo: ensure that the param is a Node?
                newNode = nodeOrId;
            }

            if (Utils.isDefined(layoutRect)) {
                newNode.bounds(layoutRect);
            }

            if (Utils.isDefined(owner)) {
                newNode.owner = owner;
            }
            this.nodes.push(newNode);
            return newNode;
        },

        /**
        * Adds the given Node and its outgoing links.
        */
        addNodeAndOutgoings: function (node) {

            if (!contains(this.nodes, node)) {
                this.nodes.push(node);
            }

            var newLinks = node.outgoing;
            node.outgoing = [];
            Utils.forEach(newLinks, function (link) {
                this.addExistingLink(link);
            }, this);
        },

        /**
        * Sets the 'index' property on the links and nodes of this graph.
        */
        setItemIndices: function () {
            var i;
            for (i = 0; i < this.nodes.length; ++i) {
                this.nodes[i].index = i;
            }

            for (i = 0; i < this.links.length; ++i) {
                this.links[i].index = i;
            }
        },

        /**
        * Returns a clone of this graph.
        */
        clone: function (saveMapping) {
            var copy = new Graph();
            var save = Utils.isDefined(saveMapping) && saveMapping === true;
            if (save) {
                copy.nodeMap = new Dictionary();
                copy.linkMap = new Dictionary();
            }
            // we need a map even if the saveMapping is not set
            var map = new Dictionary();
            Utils.forEach(this.nodes, function (nOriginal) {
                var nCopy = nOriginal.clone();
                map.set(nOriginal, nCopy);
                copy.nodes.push(nCopy);

                if (save) {
                    copy.nodeMap.set(nCopy, nOriginal);
                }
            });

            Utils.forEach(this.links, function (linkOriginal) {
                if (map.containsKey(linkOriginal.source) && map.containsKey(linkOriginal.target)) {
                    var linkCopy = copy.addLink(map.get(linkOriginal.source), map.get(linkOriginal.target));
                    if (save) {
                        copy.linkMap.set(linkCopy, linkOriginal);
                    }
                }
            });

            return copy;
        },

        /**
        * The parsing allows a quick way to create graphs.
        *  - ["n1->n2", "n2->n3"]: creates the three nodes and adds the links
        *  - ["n1->n2", {id: "QSDF"}, "n2->n3"]: same as previous but also performs a deep extend of the link between n1 and n2 with the given object.
        */
        linearize: function (addIds) {
            return Graph.Utils.linearize(this, addIds);
        },

        /**
        * Performs a depth-first traversal starting at the given node.
        * @param startNode a node or id of a node in this graph
        * @param action
        */
        depthFirstTraversal: function (startNode, action) {
            if (Utils.isUndefined(startNode)) {
                throw "You need to supply a starting node.";
            }
            if (Utils.isUndefined(action)) {
                throw "You need to supply an action.";
            }
            if (!this.hasNode(startNode)) {
                throw "The given start-node is not part of this graph";
            }
            var foundNode = this.getNode(startNode); // case the given one is an Id
            var visited = [];
            this._dftIterator(foundNode, action, visited);
        },

        _dftIterator: function (node, action, visited) {

            action(node);
            visited.push(node);
            var children = node.getChildren();
            for (var i = 0, len = children.length; i < len; i++) {
                var child = children[i];
                if (contains(visited, child)) {
                    continue;
                }
                this._dftIterator(child, action, visited);
            }
        },

        /**
        * Performs a breadth-first traversal starting at the given node.
        * @param startNode a node or id of a node in this graph
        * @param action
        */
        breadthFirstTraversal: function (startNode, action) {

            if (Utils.isUndefined(startNode)) {
                throw "You need to supply a starting node.";
            }
            if (Utils.isUndefined(action)) {
                throw "You need to supply an action.";
            }

            if (!this.hasNode(startNode)) {
                throw "The given start-node is not part of this graph";
            }
            var foundNode = this.getNode(startNode); // case the given one is an Id
            var queue = new Queue();
            var visited = [];
            queue.enqueue(foundNode);

            while (queue.length > 0) {
                var node = queue.dequeue();
                action(node);
                visited.push(node);
                var children = node.getChildren();
                for (var i = 0, len = children.length; i < len; i++) {
                    var child = children[i];
                    if (contains(visited, child) || contains(queue, child)) {
                        continue;
                    }
                    queue.enqueue(child);
                }
            }
        },

        /**
        * This is the classic Tarjan algorithm for strongly connected components.
        * See e.g. http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm
        * @param excludeSingleItems Whether isolated nodes should be excluded from the analysis.
        * @param node The start node from which the analysis starts.
        * @param indices  Numbers the nodes consecutively in the order in which they are discovered.
        * @param lowLinks The smallest index of any node known to be reachable from the node, including the node itself
        * @param connected The current component.
        * @param stack The bookkeeping stack of things to visit.
        * @param index The counter of visited nodes used to assign the indices.
        * @private
        */
        _stronglyConnectedComponents: function (excludeSingleItems, node, indices, lowLinks, connected, stack, index) {
            indices.add(node, index);
            lowLinks.add(node, index);
            index++;

            stack.push(node);

            var children = node.getChildren(), next;
            for (var i = 0, len = children.length; i < len; i++) {
                next = children[i];
                if (!indices.containsKey(next)) {
                    this._stronglyConnectedComponents(excludeSingleItems, next, indices, lowLinks, connected, stack, index);
                    lowLinks.add(node, Math.min(lowLinks.get(node), lowLinks.get(next)));
                }
                else if (contains(stack, next)) {
                    lowLinks.add(node, Math.min(lowLinks.get(node), indices.get(next)));
                }
            }
            // If v is a root node, pop the stack and generate a strong component
            if (lowLinks.get(node) === indices.get(node)) {
                var component = [];
                do {
                    next = stack.pop();
                    component.push(next);
                }
                while (next !== node);
                if (!excludeSingleItems || (component.length > 1)) {
                    connected.push(component);
                }
            }
        },

        /**
        * Returns the cycles found in this graph.
        * The returned arrays consist of the nodes which are strongly coupled.
        * @param excludeSingleItems Whether isolated nodes should be excluded.
        * @returns {Array} The array of cycles found.
        */
        findCycles: function (excludeSingleItems) {
            if (Utils.isUndefined(excludeSingleItems)) {
                excludeSingleItems = true;
            }
            var indices = new Dictionary();
            var lowLinks = new Dictionary();
            var connected = [];
            var stack = [];
            for (var i = 0, len = this.nodes.length; i < len; i++) {
                var node = this.nodes[i];
                if (indices.containsKey(node)) {
                    continue;
                }
                this._stronglyConnectedComponents(excludeSingleItems, node, indices, lowLinks, connected, stack, 0);
            }
            return connected;
        },

        /**
        * Returns whether this graph is acyclic.
        * @returns {*}
        */
        isAcyclic: function () {
            return Utils.isEmpty(this.findCycles());
        },

        /**
        * Returns whether the given graph is a subgraph of this one.
        * @param other Another graph instance.
        */
        isSubGraph: function (other) {
            var otherArray = other.linearize();
            var thisArray = this.linearize();
            return Utils.all(otherArray, function (s) {
                return contains(thisArray, s);
            });
        },

        /**
        *  Makes an acyclic graph from the current (connected) one.
        * * @returns {Array} The reversed links.
        */
        makeAcyclic: function () {
            // if empty or almost empty
            if (this.isEmpty() || this.nodes.length <= 1 || this.links.length <= 1) {
                return [];
            }
            // singular case of just two nodes
            if (this.nodes.length == 2) {
                var result = [];
                if (this.links.length > 1) {
                    var oneLink = this.links[0];
                    var oneNode = oneLink.source;
                    for (var i = 0, len = this.links.length; i < len; i++) {
                        var link = this.links[i];
                        if (link.source == oneNode) {
                            continue;
                        }
                        var rev = link.reverse();
                        result.push(rev);
                    }
                }
                return result;
            }

            var copy = this.clone(true); // copy.nodeMap tells you the mapping
            var N = this.nodes.length;

            var intensityCatalog = new Dictionary();

            /**
            * If there are both incoming and outgoing links this will return the flow intensity (out-in).
            * Otherwise the node acts as a flow source with N specifying the (equal) intensity.
            * @param node
            * @returns {number}
            */
            var flowIntensity = function (node) {
                if (node.outgoing.length === 0) {
                    return (2 - N);
                }
                else if (node.incoming.length === 0) {
                    return (N - 2);
                }
                else {
                    return node.outgoing.length - node.incoming.length;
                }
            };

            /**
            * Collects the nodes with the same intensity.
            * @param node
            * @param intensityCatalog
            */
            var catalogEqualIntensity = function (node, intensityCatalog) {
                var intensity = flowIntensity(node, N);
                if (!intensityCatalog.containsKey(intensity)) {
                    intensityCatalog.set(intensity, []);
                }
                intensityCatalog.get(intensity).push(node);
            };

            Utils.forEach(copy.nodes, function (v) {
                catalogEqualIntensity(v, intensityCatalog);
            });

            var sourceStack = [];
            var targetStack = [];

            while (copy.nodes.length > 0) {
                var source, target, intensity;
                if (intensityCatalog.containsKey(2 - N)) {
                    var targets = intensityCatalog.get(2 - N); // nodes without outgoings
                    while (targets.length > 0) {
                        target = targets.pop();
                        for (var li = 0; li < target.links.length; li++) {
                            var targetLink = target.links[li];
                            source = targetLink.getComplement(target);
                            intensity = flowIntensity(source, N);
                            Utils.remove(intensityCatalog.get(intensity), source);
                            source.removeLink(targetLink);
                            catalogEqualIntensity(source, intensityCatalog);
                        }
                        Utils.remove(copy.nodes, target);
                        targetStack.unshift(target);
                    }
                }

                // move sources to sourceStack
                if (intensityCatalog.containsKey(N - 2)) {
                    var sources = intensityCatalog.get(N - 2); // nodes without incomings
                    while (sources.length > 0) {
                        source = sources.pop();
                        for (var si = 0; si < source.links.length; si++) {
                            var sourceLink = source.links[si];
                            target = sourceLink.getComplement(source);
                            intensity = flowIntensity(target, N);
                            Utils.remove(intensityCatalog.get(intensity), target);
                            target.removeLink(sourceLink);
                            catalogEqualIntensity(target, intensityCatalog);
                        }
                        sourceStack.push(source);
                        Utils.remove(copy.nodes, source);
                    }
                }

                if (copy.nodes.length > 0) {
                    for (var k = N - 3; k > 2 - N; k--) {
                        if (intensityCatalog.containsKey(k) &&
                            intensityCatalog.get(k).length > 0) {
                            var maxdiff = intensityCatalog.get(k);
                            var v = maxdiff.pop();
                            for (var ri = 0; ri < v.links.length; ri++) {
                                var ril = v.links[ri];
                                var u = ril.getComplement(v);
                                intensity = flowIntensity(u, N);
                                Utils.remove(intensityCatalog.get(intensity), u);
                                u.removeLink(ril);
                                catalogEqualIntensity(u, intensityCatalog);
                            }
                            sourceStack.push(v);
                            Utils.remove(copy.nodes, v);
                            break;
                        }
                    }
                }
            }

            sourceStack = sourceStack.concat(targetStack);

            var vertexOrder = new Dictionary();
            for (var kk = 0; kk < this.nodes.length; kk++) {
                vertexOrder.set(copy.nodeMap.get(sourceStack[kk]), kk);
            }

            var reversedEdges = [];
            Utils.forEach(this.links, function (link) {
                if (vertexOrder.get(link.source) > vertexOrder.get(link.target)) {
                    link.reverse();
                    reversedEdges.push(link);
                }
            });
            return reversedEdges;
        }
    });

    /**
    * A collection of predefined graphs for demo and testing purposes.
    */
    Graph.Predefined = {
        /**
        * Eight-shapes graph all connected in a cycle.
        * @returns {*}
        * @constructor
        */
        EightGraph: function () {
            return Graph.Utils.parse(["1->2", "2->3", "3->4", "4->1", "3->5", "5->6", "6->7", "7->3"]);
        },

        /**
        * Creates a typical mindmap diagram.
        * @returns {*}
        * @constructor
        */
        Mindmap: function () {
            return Graph.Utils.parse(["0->1", "0->2", "0->3", "0->4", "0->5", "1->6", "1->7", "7->8", "2->9", "9->10", "9->11", "3->12",
                "12->13", "13->14", "4->15", "4->16", "15->17", "15->18", "18->19", "18->20", "14->21", "14->22", "5->23", "23->24", "23->25", "6->26"]);
        },

        /**
        * Three nodes connected in a cycle.
        * @returns {*}
        * @constructor
        */
        ThreeGraph: function () {
            return Graph.Utils.parse(["1->2", "2->3", "3->1"]);
        },

        /**
        * A tree with each node having two children.
        * @param levels How many levels the binary tree should have.
        * @returns {diagram.Graph}
        * @constructor
        */
        BinaryTree: function (levels) {
            if (Utils.isUndefined(levels)) {
                levels = 5;
            }
            return Graph.Utils.createBalancedTree(levels, 2);
        },

        /**
        * A linear graph (discrete line segment).
        * @param length How many segments (the node count is hence (length+1)).
        * @returns {diagram.Graph}
        * @constructor
        */
        Linear: function (length) {
            if (Utils.isUndefined(length)) {
                length = 10;
            }
            return Graph.Utils.createBalancedTree(length, 1);
        },

        /**
        * A standard tree-graph with the specified levels and children (siblings) count.
        * Note that for a balanced tree of level N and sibling count s, counting the root as level zero:
        *  - NodeCount = (1-s^(N+1))/(1-s)]
        *  - LinkCount = s.(1-s^N)/(1-s)
        * @param levels How many levels the tree should have.
        * @param siblingsCount How many siblings each level should have.
        * @returns {diagram.Graph}
        * @constructor
        */
        Tree: function (levels, siblingsCount) {
            return Graph.Utils.createBalancedTree(levels, siblingsCount);
        },

        /**
        * Creates a forest.
        * Note that for a balanced forest of level N, sibling count s and tree count t, counting the root as level zero:
        *  - NodeCount = t.(1-s^(N+1))/(1-s)]
        *  - LinkCount = t.s.(1-s^N)/(1-s)
        * @param levels How many levels the tree should have.
        * @param siblingsCount How many siblings each level should have.
        * @param trees The amount of trees the forest should have.
        * @returns {diagram.Graph}
        * @constructor
        */
        Forest: function (levels, siblingsCount, trees) {
            return Graph.Utils.createBalancedForest(levels, siblingsCount, trees);
        },

        /**
        * A workflow-like graph with cycles.
        * @returns {*}
        * @constructor
        */
        Workflow: function () {
            return Graph.Utils.parse(
                ["0->1", "1->2", "2->3", "1->4", "4->3", "3->5", "5->6", "6->3", "6->7", "5->4"]
            );
        },

        /**
        * A grid graph with the direction of the links avoiding cycles.
        * Node count: (n+1).(m+1)
        * Link count: n.(m+1) + m.(n+1)
        * @param n Horizontal count of grid cells. If zero this will result in a linear graph.
        * @param m Vertical count of grid cells. If zero this will result in a linear graph.
        * @constructor
        */
        Grid: function (n, m) {
            var g = new diagram.Graph();
            if (n <= 0 && m <= 0) {
                return g;
            }

            for (var i = 0; i < n + 1; i++) {
                var previous = null;
                for (var j = 0; j < m + 1; j++) {
                    // using x-y coordinates to name the nodes
                    var node = new Node(i.toString() + "." + j.toString());
                    g.addNode(node);
                    if (previous) {
                        g.addLink(previous, node);
                    }
                    if (i > 0) {
                        var left = g.getNode((i - 1).toString() + "." + j.toString());
                        g.addLink(left, node);
                    }
                    previous = node;
                }
            }
            return g;
        }

    };

    /**
    * Graph generation and other utilities.
    */
    Graph.Utils = {
        /**
        * The parsing allows a quick way to create graphs.
        *  - ["n1->n2", "n2->n3"]: creates the three nodes and adds the links
        *  - ["n1->n2", {id: "id177"}, "n2->n3"]: same as previous but also performs a deep extend of the link between n1 and n2 with the given object.
        */
        parse: function (graphString) {

            var previousLink, graph = new diagram.Graph(), parts = graphString.slice();
            for (var i = 0, len = parts.length; i < len; i++) {
                var part = parts[i];
                if (Utils.isString(part)) // link spec
                {
                    if (part.indexOf("->") < 0) {
                        throw "The link should be specified as 'a->b'.";
                    }
                    var p = part.split("->");
                    if (p.length != 2) {
                        throw "The link should be specified as 'a->b'.";
                    }
                    previousLink = new Link(p[0], p[1]);
                    graph.addLink(previousLink);
                }
                if (Utils.isObject(part)) {
                    if (!previousLink) {
                        throw "Specification found before Link definition.";
                    }
                    kendo.deepExtend(previousLink, part);
                }
            }
            return graph;
        },

        /**
        * Returns a linearized representation of the given Graph.
        * See also the Graph.Utils.parse method for the inverse operation.
        */
        linearize: function (graph, addIds) {
            if (Utils.isUndefined(graph)) {
                throw "Expected an instance of a Graph object in slot one.";
            }
            if (Utils.isUndefined(addIds)) {
                addIds = false;
            }
            var lin = [];
            for (var i = 0, len = graph.links.length; i < len; i++) {
                var link = graph.links[i];
                lin.push(link.source.id + "->" + link.target.id);
                if (addIds) {
                    lin.push({ id: link.id });
                }
            }
            return lin;
        },

        /**
        * The method used by the diagram creation to instantiate a shape.
        * @param kendoDiagram The Kendo diagram where the diagram will be created.
        * @param p The position at which to place the shape.
        * @param shapeDefaults Optional Shape options.
        * @param id Optional identifier of the shape.
        * @returns {*}
        * @private
        */
        _addShape: function (kendoDiagram, p, id, shapeDefaults) {
            if (Utils.isUndefined(p)) {
                p = new diagram.Point(0, 0);
            }

            if (Utils.isUndefined(id)) {
                id = randomId();
            }

            shapeDefaults = kendo.deepExtend({
                width: 20,
                height: 20,
                id: id,
                radius: 10,
                fill: "#778899",
                data: "circle",
                undoable: false,
                x: p.x,
                y: p.y
            }, shapeDefaults);

            return kendoDiagram.addShape(shapeDefaults);
        },
        /**
        * The method used by the diagram creation to instantiate a connection.
        * @param diagram he Kendo diagram where the diagram will be created.
        * @param from The source shape.
        * @param to The target shape.
        * @param options Optional Connection options.
        * @returns {*}
        * @private
        */
        _addConnection: function (diagram, from, to, options) {
            return diagram.connect(from, to, options);
        },

        /**
        * Creates a diagram from the given Graph.
        * @param diagram The Kendo diagram where the diagram will be created.
        * @param graph The graph structure defining the diagram.
        */
        createDiagramFromGraph: function (diagram, graph, doLayout, randomSize) {

            if (Utils.isUndefined(diagram)) {
                throw "The diagram surface is undefined.";
            }
            if (Utils.isUndefined(graph)) {
                throw "No graph specification defined.";
            }
            if (Utils.isUndefined(doLayout)) {
                doLayout = true;
            }
            if (Utils.isUndefined(randomSize)) {
                randomSize = false;
            }

            var width = diagram.element.clientWidth || 200;
            var height = diagram.element.clientHeight || 200;
            var map = [], node, shape;
            for (var i = 0, len = graph.nodes.length; i < len; i++) {
                node = graph.nodes[i];
                var p = node.position;
                if (Utils.isUndefined(p)) {
                    if (Utils.isDefined(node.x) && Utils.isDefined(node.y)) {
                        p = new Point(node.x, node.y);
                    }
                    else {
                        p = new Point(Utils.randomInteger(10, width - 20), Utils.randomInteger(10, height - 20));
                    }
                }
                var opt = {};

                if (node.id === "0") {
                    /* kendo.deepExtend(opt,
                    {
                    fill: "Orange",
                    data: 'circle',
                    width: 100,
                    height: 100,
                    center: new Point(50, 50)
                    });*/
                }
                else if (randomSize) {
                    kendo.deepExtend(opt, {
                        width: Math.random() * 150 + 20,
                        height: Math.random() * 80 + 50,
                        data: 'rectangle',
                        fill: {
                            color: "#778899"
                        }
                    });
                }

                shape = this._addShape(diagram, p, node.id, opt);
                //shape.content(node.id);

                var bounds = shape.bounds();
                if (Utils.isDefined(bounds)) {
                    node.x = bounds.x;
                    node.y = bounds.y;
                    node.width = bounds.width;
                    node.height = bounds.height;
                }
                map[node.id] = shape;
            }
            for (var gli = 0; gli < graph.links.length; gli++) {
                var link = graph.links[gli];
                var sourceShape = map[link.source.id];
                if (Utils.isUndefined(sourceShape)) {
                    continue;
                }
                var targetShape = map[link.target.id];
                if (Utils.isUndefined(targetShape)) {
                    continue;
                }
                this._addConnection(diagram, sourceShape, targetShape, { id: link.id });

            }
            if (doLayout) {
                var l = new diagram.SpringLayout(diagram);
                l.layoutGraph(graph, { limitToView: false });
                for (var shi = 0; shi < graph.nodes.length; shi++) {
                    node = graph.nodes[shi];
                    shape = map[node.id];
                    shape.bounds(new Rect(node.x, node.y, node.width, node.height));
                }
            }
        },

        /**
        * Creates a balanced tree with the specified number of levels and siblings count.
        * Note that for a balanced tree of level N and sibling count s, counting the root as level zero:
        *  - NodeCount = (1-s^(N+1))/(1-s)]
        *  - LinkCount = s.(1-s^N)/(1-s)
        * @param levels How many levels the tree should have.
        * @param siblingsCount How many siblings each level should have.
        * @returns {diagram.Graph}
        */
        createBalancedTree: function (levels, siblingsCount) {
            if (Utils.isUndefined(levels)) {
                levels = 3;
            }
            if (Utils.isUndefined(siblingsCount)) {
                siblingsCount = 3;
            }

            var g = new diagram.Graph(), counter = -1, lastAdded = [], news;
            if (levels <= 0 || siblingsCount <= 0) {
                return g;
            }
            var root = new Node((++counter).toString());
            g.addNode(root);
            g.root = root;
            lastAdded.push(root);
            for (var i = 0; i < levels; i++) {
                news = [];
                for (var j = 0; j < lastAdded.length; j++) {
                    var parent = lastAdded[j];
                    for (var k = 0; k < siblingsCount; k++) {
                        var item = new Node((++counter).toString());
                        g.addLink(parent, item);
                        news.push(item);
                    }
                }
                lastAdded = news;
            }
            return g;
        },

        /**
        * Creates a balanced tree with the specified number of levels and siblings count.
        * Note that for a balanced forest of level N, sibling count s and tree count t, counting the root as level zero:
        *  - NodeCount = t.(1-s^(N+1))/(1-s)]
        *  - LinkCount = t.s.(1-s^N)/(1-s)
        * @param levels How many levels the tree should have.
        * @param siblingsCount How many siblings each level should have.
        * @returns {diagram.Graph}
        * @param treeCount The number of trees the forest should have.
        */
        createBalancedForest: function (levels, siblingsCount, treeCount) {
            if (Utils.isUndefined(levels)) {
                levels = 3;
            }
            if (Utils.isUndefined(siblingsCount)) {
                siblingsCount = 3;
            }
            if (Utils.isUndefined(treeCount)) {
                treeCount = 5;
            }
            var g = new diagram.Graph(), counter = -1, lastAdded = [], news;
            if (levels <= 0 || siblingsCount <= 0 || treeCount <= 0) {
                return g;
            }

            for (var t = 0; t < treeCount; t++) {
                var root = new Node((++counter).toString());
                g.addNode(root);
                lastAdded = [root];
                for (var i = 0; i < levels; i++) {
                    news = [];
                    for (var j = 0; j < lastAdded.length; j++) {
                        var parent = lastAdded[j];
                        for (var k = 0; k < siblingsCount; k++) {
                            var item = new Node((++counter).toString());
                            g.addLink(parent, item);
                            news.push(item);
                        }
                    }
                    lastAdded = news;
                }
            }
            return g;
        },

        /**
        * Creates a random graph (uniform distribution) with the specified amount of nodes.
        * @param nodeCount The amount of nodes the random graph should have.
        * @param maxIncidence The maximum allowed degree of the nodes.
        * @param isTree Whether the return graph should be a tree (default: false).
        * @returns {diagram.Graph}
        */
        createRandomConnectedGraph: function (nodeCount, maxIncidence, isTree) {

            /* Swa's Mathematica export of random Bernoulli graphs
            gr[n_,p_]:=Module[{g=RandomGraph[BernoulliGraphDistribution[n,p],VertexLabels->"Name",DirectedEdges->True]},
            While[Not[ConnectedGraphQ[g]],g=RandomGraph[BernoulliGraphDistribution[n,p],VertexLabels->"Name",DirectedEdges->True]];g];
            project[a_]:=("\""<>ToString[Part[#,1]]<>"->"<>ToString[Part[#,2]]<>"\"")&     @ a;
            export[g_]:=project/@ EdgeList[g]
            g = gr[12,.1]
            export [g]
            */

            if (Utils.isUndefined(nodeCount)) {
                nodeCount = 40;
            }
            if (Utils.isUndefined(maxIncidence)) {
                maxIncidence = 4;
            }
            if (Utils.isUndefined(isTree)) {
                isTree = false;
            }

            var g = new diagram.Graph(), counter = -1;
            if (nodeCount <= 0) {
                return g;
            }

            var root = new Node((++counter).toString());
            g.addNode(root);
            if (nodeCount === 1) {
                return g;
            }
            if (nodeCount > 1) {
                // random tree
                for (var i = 1; i < nodeCount; i++) {
                    var poolNode = g.takeRandomNode([], maxIncidence);
                    if (!poolNode) {
                        //failed to find one so the graph will have less nodes than specified
                        break;
                    }
                    var newNode = g.addNode(i.toString());
                    g.addLink(poolNode, newNode);
                }
                if (!isTree && nodeCount > 1) {
                    var randomAdditions = Utils.randomInteger(1, nodeCount);
                    for (var ri = 0; ri < randomAdditions; ri++) {
                        var n1 = g.takeRandomNode([], maxIncidence);
                        var n2 = g.takeRandomNode([], maxIncidence);
                        if (n1 && n2 && !g.areConnected(n1, n2)) {
                            g.addLink(n1, n2);
                        }
                    }
                }
                return g;
            }
        },

        /**
        * Generates a random diagram.
        * @param diagram The host diagram.
        * @param shapeCount The number of shapes the random diagram should contain.
        * @param maxIncidence The maximum degree the shapes can have.
        * @param isTree Whether the generated diagram should be a tree
        * @param layoutType The optional layout type to apply after the diagram is generated.
        */
        randomDiagram: function (diagram, shapeCount, maxIncidence, isTree, randomSize) {
            var g = kendo.dataviz.diagram.Graph.Utils.createRandomConnectedGraph(shapeCount, maxIncidence, isTree);
            Graph.Utils.createDiagramFromGraph(diagram, g, false, randomSize);
        }
    };

    kendo.deepExtend(diagram, {
        init: function (element) {
            kendo.init(element, diagram.ui);
        },

        Point: Point,
        Intersect: Intersect,
        Geometry: Geometry,
        Rect: Rect,
        Size: Size,
        RectAlign: RectAlign,
        Matrix: Matrix,
        MatrixVector: MatrixVector,
        normalVariable: normalVariable,
        randomId: randomId,
        Dictionary: Dictionary,
        HashTable: HashTable,
        Queue: Queue,
        Set: Set,
        Node: Node,
        Link: Link,
        Graph: Graph,
        PathDefiner: PathDefiner
    });
})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var kendo = window.kendo,
        Observable = kendo.Observable,
        diagram = kendo.dataviz.diagram,
        Class = kendo.Class,
        deepExtend = kendo.deepExtend,
        dataviz = kendo.dataviz,
        Point = diagram.Point,
        Rect = diagram.Rect,
        RectAlign = diagram.RectAlign,
        Matrix = diagram.Matrix,
        Utils = diagram.Utils,
        isUndefined = Utils.isUndefined,
        isNumber = Utils.isNumber,
        isString = Utils.isString,
        MatrixVector = diagram.MatrixVector,

        g = kendo.geometry,
        d = kendo.drawing,

        defined = kendo.util.defined,

        inArray = $.inArray;

    // Constants ==============================================================
    var TRANSPARENT = "transparent",
        Markers = {
            none: "none",
            arrowStart: "ArrowStart",
            filledCircle: "FilledCircle",
            arrowEnd: "ArrowEnd"
        },
        DEFAULTWIDTH = 100,
        DEFAULTHEIGHT = 100,
        FULL_CIRCLE_ANGLE = 360,
        START = "start",
        END = "end",
        WIDTH = "width",
        HEIGHT = "height",
        X = "x",
        Y = "y";

    diagram.Markers = Markers;

    var Scale = Class.extend({
        init: function (x, y) {
            this.x = x;
            this.y = y;
        },
        toMatrix: function () {
            return Matrix.scaling(this.x, this.y);
        },
        toString: function () {
            return kendo.format("scale({0},{1})", this.x, this.y);
        },
        invert: function () {
            return new Scale(1 / this.x, 1 / this.y);
        }
    });

    var Translation = Class.extend({
        init: function (x, y) {
            this.x = x;
            this.y = y;
        },
        toMatrixVector: function () {
            return new MatrixVector(0, 0, 0, 0, this.x, this.y);
        },
        toMatrix: function () {
            return Matrix.translation(this.x, this.y);
        },
        toString: function () {
            return kendo.format("translate({0},{1})", this.x, this.y);
        },
        plus: function (delta) {
            this.x += delta.x;
            this.y += delta.y;
        },
        times: function (factor) {
            this.x *= factor;
            this.y *= factor;
        },
        length: function () {
            return Math.sqrt(this.x * this.x + this.y * this.y);
        },
        normalize: function () {
            if (this.Length === 0) {
                return;
            }
            this.times(1 / this.length());
        },
        invert: function () {
            return new Translation(-this.x, -this.y);
        }
    });

    var Rotation = Class.extend({
        init: function (angle, x, y) {
            this.x = x || 0;
            this.y = y || 0;
            this.angle = angle;
        },
        toString: function () {
            if (this.x && this.y) {
                return kendo.format("rotate({0},{1},{2})", this.angle, this.x, this.y);
            } else {
                return kendo.format("rotate({0})", this.angle);
            }
        },
        toMatrix: function () {
            return Matrix.rotation(this.angle, this.x, this.y); // T*R*T^-1
        },
        center: function () {
            return new Point(this.x, this.y);
        },
        invert: function () {
            return new Rotation(FULL_CIRCLE_ANGLE - this.angle, this.x, this.y);
        }
    });

    Rotation.create = function (rotation) {
        return new Rotation(rotation.angle, rotation.x, rotation.y);
    };

    Rotation.parse = function (str) {
        var values = str.slice(1, str.length - 1).split(","),
            angle = values[0],
            x = values[1],
            y = values[2];
        var rotation = new Rotation(angle, x, y);
        return rotation;
    };

    var CompositeTransform = Class.extend({
        init: function (x, y, scaleX, scaleY, angle, center) {
            this.translate = new Translation(x, y);
            if (scaleX !== undefined && scaleY !== undefined) {
                this.scale = new Scale(scaleX, scaleY);
            }
            if (angle !== undefined) {
                this.rotate = center ? new Rotation(angle, center.x, center.y) : new Rotation(angle);
            }
        },
        toString: function () {
            var toString = function (transform) {
                return transform ? transform.toString() : "";
            };

            return toString(this.translate) +
                toString(this.rotate) +
                toString(this.scale);
        },

        render: function (visual) {
            visual._transform = this;
            visual._renderTransform();
        },

        toMatrix: function () {
            var m = Matrix.unit();

            if (this.translate) {
                m = m.times(this.translate.toMatrix());
            }
            if (this.rotate) {
                m = m.times(this.rotate.toMatrix());
            }
            if (this.scale) {
                m = m.times(this.scale.toMatrix());
            }
            return m;
        },
        invert: function () {
            var rotate = this.rotate ? this.rotate.invert() : undefined,
                rotateMatrix = rotate ? rotate.toMatrix() : Matrix.unit(),
                scale = this.scale ? this.scale.invert() : undefined,
                scaleMatrix = scale ? scale.toMatrix() : Matrix.unit();

            var translatePoint = new Point(-this.translate.x, -this.translate.y);
            translatePoint = rotateMatrix.times(scaleMatrix).apply(translatePoint);
            var translate = new Translation(translatePoint.x, translatePoint.y);

            var transform = new CompositeTransform();
            transform.translate = translate;
            transform.rotate = rotate;
            transform.scale = scale;

            return transform;
        }
    });

    var AutoSizeableMixin = {
        _setScale: function () {
            var options = this.options;
            var originWidth = this._originWidth;
            var originHeight = this._originHeight;
            var scaleX = options.width / originWidth;
            var scaleY = options.height / originHeight;

            if (!isNumber(scaleX)) {
                scaleX = 1;
            }
            if (!isNumber(scaleY)) {
                scaleY = 1;
            }

            this._transform.scale = new Scale(scaleX, scaleY);
        },

        _setTranslate: function () {
            var options = this.options;
            var x = options.x || 0;
            var y = options.y || 0;
            this._transform.translate = new Translation(x, y);
        },

        _initSize: function () {
            var options = this.options;
            var transform = false;
            if (options.autoSize !== false && (defined(options.width) || defined(options.height))) {
                this._measure(true);
                this._setScale();
                transform = true;
            }

            if (defined(options.x) || defined(options.y)) {
                this._setTranslate();
                transform = true;
            }

            if (transform) {
                this._renderTransform();
            }
        },

        _updateSize: function (options) {
            var update = false;

            if (this.options.autoSize !== false && this._diffNumericOptions(options, [WIDTH, HEIGHT])) {
                update = true;
                this._measure(true);
                this._setScale();
            }

            if (this._diffNumericOptions(options, [X, Y])) {
                update = true;
                this._setTranslate();
            }

            if (update) {
                this._renderTransform();
            }

            return update;
        }
    };

    var Element = Class.extend({
        init: function (options) {
            var element = this;
            element.options = deepExtend({}, element.options, options);
            element.id = element.options.id;
            element._originSize = Rect.empty();
            element._transform = new CompositeTransform();
        },

        visible: function (value) {
            return this.drawingContainer().visible(value);
        },

        redraw: function (options) {
            if (options && options.id) {
                this.id = options.id;
            }
        },

        position: function (x, y) {
            var options = this.options;
            if (!defined(x)) {
                return new Point(options.x, options.y);
            }

            if (defined(y)) {
                options.x = x;
                options.y = y;
            } else if (x instanceof Point) {
                options.x = x.x;
                options.y = x.y;
            }

            this._transform.translate = new Translation(options.x, options.y);
            this._renderTransform();
        },

        rotate: function (angle, center) {
            if (defined(angle)) {
                this._transform.rotate = new Rotation(angle, center.x, center.y);
                this._renderTransform();
            }
            return this._transform.rotate || new Rotation(0);
        },

        drawingContainer: function () {
            return this.drawingElement;
        },

        _renderTransform: function () {
            var matrix = this._transform.toMatrix();
            this.drawingContainer().transform(new g.Matrix(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f));
        },

        _hover: function () { },

        _diffNumericOptions: diffNumericOptions,

        _measure: function (force) {
            var rect;
            if (!this._measured || force) {
                var box = this._boundingBox() || new g.Rect();
                var startPoint = box.topLeft();
                rect = new Rect(startPoint.x, startPoint.y, box.width(), box.height());
                this._originSize = rect;
                this._originWidth = rect.width;
                this._originHeight = rect.height;
                this._measured = true;
            } else {
                rect = this._originSize;
            }
            return rect;
        },

        _boundingBox: function () {
            return this.drawingElement.rawBBox();
        }
    });

    var VisualBase = Element.extend({
        init: function (options) {
            Element.fn.init.call(this, options);

            options = this.options;
            options.fill = normalizeDrawingOptions(options.fill);
            options.stroke = normalizeDrawingOptions(options.stroke);
        },

        options: {
            stroke: {
                color: "gray",
                width: 1
            },
            fill: {
                color: TRANSPARENT
            }
        },

        fill: function (color, opacity) {
            this._fill({
                color: getColor(color),
                opacity: opacity
            });
        },

        stroke: function (color, width, opacity) {
            this._stroke({
                color: getColor(color),
                width: width,
                opacity: opacity
            });
        },

        redraw: function (options) {
            if (options) {
                var stroke = options.stroke;
                var fill = options.fill;
                if (stroke) {
                    this._stroke(normalizeDrawingOptions(stroke));
                }
                if (fill) {
                    this._fill(normalizeDrawingOptions(fill));
                }

                Element.fn.redraw.call(this, options);
            }
        },

        _hover: function (show) {
            var drawingElement = this.drawingElement;
            var options = this.options;
            var hover = options.hover;

            if (hover && hover.fill) {
                var fill = show ? normalizeDrawingOptions(hover.fill) : options.fill;
                drawingElement.fill(fill.color, fill.opacity);
            }
        },

        _stroke: function (strokeOptions) {
            var options = this.options;
            deepExtend(options, {
                stroke: strokeOptions
            });

            strokeOptions = options.stroke;

            var stroke = null;
            if (strokeOptions.width > 0) {
                stroke = {
                    color: strokeOptions.color,
                    width: strokeOptions.width,
                    opacity: strokeOptions.opacity,
                    dashType: strokeOptions.dashType
                };
            }

            this.drawingElement.options.set("stroke", stroke);
        },

        _fill: function (fillOptions) {
            var options = this.options;
            deepExtend(options, {
                fill: fillOptions
            });
            var fill = options.fill;

            this.drawingElement.fill(fill.color, fill.opacity);
        }
    });

    var TextBlock = VisualBase.extend({
        init: function (options) {
            this._textColor(options);

            VisualBase.fn.init.call(this, options);

            this._font();
            this._initText();
            this._initSize();
        },

        options: {
            fontSize: 15,
            fontFamily: "sans-serif",
            stroke: {
                width: 0
            },
            fill: {
                color: "black"
            },
            autoSize: true
        },

        _initText: function () {
            var options = this.options;

            this.drawingElement = new d.Text(defined(options.text) ? options.text : "", new g.Point(), {
                fill: options.fill,
                font: options.font
            });

            this._stroke();
        },

        _textColor: function (options) {
            if (options && options.color) {
                deepExtend(options, {
                    fill: {
                        color: options.color
                    }
                });
            }
        },

        _font: function () {
            var options = this.options;
            if (options.fontFamily && defined(options.fontSize)) {
                options.font = options.fontSize + "px " + options.fontFamily;
            } else {
                delete options.font;
            }
        },

        content: function (text) {
            return this.drawingElement.content(text);
        },

        redraw: function (options) {
            if (options) {
                var sizeChanged = false;
                var textOptions = this.options;

                this._textColor(options);

                VisualBase.fn.redraw.call(this, options);

                if (options.fontFamily || defined(options.fontSize)) {
                    deepExtend(textOptions, {
                        fontFamily: options.fontFamily,
                        fontSize: options.fontSize
                    });
                    this._font();
                    this.drawingElement.options.set("font", textOptions.font);
                    sizeChanged = true;
                }

                if (options.text) {
                    this.content(options.text);
                    sizeChanged = true;
                }

                if (!this._updateSize(options) && sizeChanged) {
                    this._initSize();
                }
            }
        }
    });

    deepExtend(TextBlock.fn, AutoSizeableMixin);

    var Rectangle = VisualBase.extend({
        init: function (options) {
            VisualBase.fn.init.call(this, options);
            this._initPath();
            this._setPosition();
        },

        _setPosition: function () {
            var options = this.options;
            var x = options.x;
            var y = options.y;
            if (defined(x) || defined(y)) {
                this.position(x || 0, y || 0);
            }
        },

        redraw: function (options) {
            if (options) {
                VisualBase.fn.redraw.call(this, options);
                if (this._diffNumericOptions(options, [WIDTH, HEIGHT])) {
                    this._drawPath();
                }
                if (this._diffNumericOptions(options, [X, Y])) {
                    this._setPosition();
                }
            }
        },

        _initPath: function () {
            var options = this.options;
            var drawingElement = this.drawingElement = new d.Path({
                fill: options.fill,
                stroke: options.stroke,
                closed: true
            });
            this._drawPath();
        },

        _drawPath: function () {
            var drawingElement = this.drawingElement;
            var sizeOptions = sizeOptionsOrDefault(this.options);
            var width = sizeOptions.width;
            var height = sizeOptions.height;

            drawingElement.segments.elements([
                createSegment(0, 0),
                createSegment(width, 0),
                createSegment(width, height),
                createSegment(0, height)
            ]);
        }
    });

    var MarkerBase = VisualBase.extend({
        init: function (options) {
            VisualBase.fn.init.call(this, options);
            var anchor = this.options.anchor;
            this.anchor = new g.Point(anchor.x, anchor.y);
            this.createElement();
        },

        options: {
            stroke: {
                color: TRANSPARENT,
                width: 0
            },
            fill: {
                color: "black"
            }
        },

        _transformToPath: function (point, path) {
            var transform = path.transform();
            if (point && transform) {
                point = point.transformCopy(transform);
            }
            return point;
        },

        redraw: function (options) {
            if (options) {
                if (options.position) {
                    this.options.position = options.position;
                }

                VisualBase.fn.redraw.call(this, options);
            }
        }
    });

    var CircleMarker = MarkerBase.extend({
        options: {
            radius: 4,
            anchor: {
                x: 0,
                y: 0
            }
        },

        createElement: function () {
            var options = this.options;
            this.drawingElement = new d.Circle(new g.Circle(this.anchor, options.radius), {
                fill: options.fill,
                stroke: options.stroke
            });
        },

        positionMarker: function (path) {
            var options = this.options;
            var position = options.position;
            var segments = path.segments;
            var targetSegment;
            var point;

            if (position == START) {
                targetSegment = segments[0];
            } else {
                targetSegment = segments[segments.length - 1];
            }
            if (targetSegment) {
                point = this._transformToPath(targetSegment.anchor(), path);
                this.drawingElement.transform(g.transform().translate(point.x, point.y));
            }
        }
    });

    var ArrowMarker = MarkerBase.extend({
        options: {
            path: "M 0 0 L 10 5 L 0 10 L 3 5 z",
            anchor: {
                x: 10,
                y: 5
            }
        },

        createElement: function () {
            var options = this.options;
            this.drawingElement = d.Path.parse(options.path, {
                fill: options.fill,
                stroke: options.stroke
            });
        },

        positionMarker: function (path) {
            var points = this._linePoints(path);
            var start = points.start;
            var end = points.end;
            var transform = g.transform();
            if (start) {
                transform.rotate(lineAngle(start, end), end);
            }

            if (end) {
                var anchor = this.anchor;
                var translate = end.clone().translate(-anchor.x, -anchor.y);
                transform.translate(translate.x, translate.y);
            }
            this.drawingElement.transform(transform);
        },

        _linePoints: function (path) {
            var options = this.options;
            var segments = path.segments;
            var startPoint, endPoint, targetSegment;
            if (options.position == START) {
                targetSegment = segments[0];
                if (targetSegment) {
                    endPoint = targetSegment.anchor();
                    startPoint = targetSegment.controlOut();
                    var nextSegment = segments[1];
                    if (!startPoint && nextSegment) {
                        startPoint = nextSegment.anchor();
                    }
                }
            } else {
                targetSegment = segments[segments.length - 1];
                if (targetSegment) {
                    endPoint = targetSegment.anchor();
                    startPoint = targetSegment.controlIn();
                    var prevSegment = segments[segments.length - 2];
                    if (!startPoint && prevSegment) {
                        startPoint = prevSegment.anchor();
                    }
                }
            }
            if (endPoint) {
                return {
                    start: this._transformToPath(startPoint, path),
                    end: this._transformToPath(endPoint, path)
                };
            }
        }
    });

    var MarkerPathMixin = {
        _getPath: function (position) {
            var path = this.drawingElement;
            if (path instanceof d.MultiPath) {
                if (position == START) {
                    path = path.paths[0];
                } else {
                    path = path.paths[path.paths.length - 1];
                }
            }
            if (path && path.segments.length) {
                return path;
            }
        },

        _removeMarker: function (position) {
            var marker = this._markers[position];
            if (marker) {
                this.drawingContainer().remove(marker.drawingElement);
                delete this._markers[position];
            }
        },

        _createMarkers: function () {
            var options = this.options;
            var startCap = options.startCap;
            var endCap = options.endCap;
            this._markers = {};
            this._markers[START] = this._createMarker(startCap, START);
            this._markers[END] = this._createMarker(endCap, END);
        },

        _createMarker: function (type, position) {
            var path = this._getPath(position);
            var markerType, marker;
            if (!path) {
                this._removeMarker(position);
                return;
            }

            if (type == Markers.filledCircle) {
                markerType = CircleMarker;
            } else if (type == Markers.arrowStart || type == Markers.arrowEnd) {
                markerType = ArrowMarker;
            } else {
                this._removeMarker(position);
            }
            if (markerType) {
                marker = new markerType({
                    position: position
                });
                marker.positionMarker(path);
                this.drawingContainer().append(marker.drawingElement);

                return marker;
            }
        },

        _positionMarker: function (position) {
            var marker = this._markers[position];

            if (marker) {
                var path = this._getPath(position);
                if (path) {
                    marker.positionMarker(path);
                } else {
                    this._removeMarker(position);
                }
            }
        },

        _capMap: {
            start: "startCap",
            end: "endCap"
        },

        _redrawMarker: function (pathChange, position, options) {
            var pathOptions = this.options;
            var cap = this._capMap[position];
            var optionsCap = options[cap];
            var created = false;
            if (optionsCap && pathOptions[cap] != optionsCap) {
                pathOptions[cap] = optionsCap;
                this._removeMarker(position);
                this._markers[position] = this._createMarker(optionsCap, position);
                created = true;
            } else if (pathChange && !this._markers[position] && pathOptions[cap]) {
                this._markers[position] = this._createMarker(pathOptions[cap], position);
                created = true;
            }
            return created;
        },

        _redrawMarkers: function (pathChange, options) {
            if (!this._redrawMarker(pathChange, START, options) && pathChange) {
                this._positionMarker(START);
            }
            if (!this._redrawMarker(pathChange, END, options) && pathChange) {
                this._positionMarker(END);
            }
        }
    };

    var Path = VisualBase.extend({
        init: function (options) {
            VisualBase.fn.init.call(this, options);
            this.container = new d.Group();
            this._createElements();
            this._initSize();
        },

        options: {
            autoSize: true
        },

        drawingContainer: function () {
            return this.container;
        },

        data: function (value) {
            var options = this.options;
            if (value) {
                if (options.data != value) {
                    options.data = value;
                    this._setData(value);
                    this._initSize();
                    this._redrawMarkers(true, {});
                }
            } else {
                return options.data;
            }
        },

        redraw: function (options) {
            if (options) {
                VisualBase.fn.redraw.call(this, options);

                var pathOptions = this.options;
                var data = options.data;

                if (defined(data) && pathOptions.data != data) {
                    pathOptions.data = data;
                    this._setData(data);
                    if (!this._updateSize(options)) {
                        this._initSize();
                    }
                    this._redrawMarkers(true, options);
                } else {
                    this._updateSize(options);
                    this._redrawMarkers(false, options);
                }
            }
        },

        _createElements: function () {
            var options = this.options;

            this.drawingElement = d.Path.parse(options.data || "", {
                fill: options.fill,
                stroke: options.stroke
            });
            this.container.append(this.drawingElement);
            this._createMarkers();
        },

        _setData: function (data) {
            var drawingElement = this.drawingElement;
            var multipath = d.Path.parse(data || "");
            var paths = multipath.paths.slice(0);
            multipath.paths.elements([]);
            drawingElement.paths.elements(paths);
        }
    });

    deepExtend(Path.fn, AutoSizeableMixin);
    deepExtend(Path.fn, MarkerPathMixin);

    var Line = VisualBase.extend({
        init: function (options) {
            VisualBase.fn.init.call(this, options);
            this.container = new d.Group();
            this._initPath();
            this._createMarkers();
        },

        drawingContainer: function () {
            return this.container;
        },

        redraw: function (options) {
            if (options) {
                options = options || {};
                var from = options.from;
                var to = options.to;
                if (from) {
                    this.options.from = from;
                }

                if (to) {
                    this.options.to = to;
                }

                if (from || to) {
                    this._drawPath();
                    this._redrawMarkers(true, options);
                } else {
                    this._redrawMarkers(false, options);
                }

                VisualBase.fn.redraw.call(this, options);
            }
        },

        _initPath: function () {
            var options = this.options;
            var drawingElement = this.drawingElement = new d.Path({
                fill: options.fill,
                stroke: options.stroke
            });
            this._drawPath();
            this.container.append(drawingElement);
        },

        _drawPath: function () {
            var options = this.options;
            var drawingElement = this.drawingElement;
            var from = options.from || new Point();
            var to = options.to || new Point();

            drawingElement.segments.elements([
                createSegment(from.x, from.y),
                createSegment(to.x, to.y)
            ]);
        }
    });

    deepExtend(Line.fn, MarkerPathMixin);

    var Polyline = VisualBase.extend({
        init: function (options) {
            VisualBase.fn.init.call(this, options);
            this.container = new d.Group();
            this._initPath();
            this._createMarkers();
        },

        drawingContainer: function () {
            return this.container;
        },

        points: function (points) {
            var options = this.options;
            if (points) {
                options.points = points;
                this._updatePath();
            } else {
                return options.points;
            }
        },

        redraw: function (options) {
            if (options) {
                var points = options.points;
                VisualBase.fn.redraw.call(this, options);

                if (points && this._pointsDiffer(points)) {
                    this.points(points);
                    this._redrawMarkers(true, options);
                } else {
                    this._redrawMarkers(false, options);
                }
            }
        },

        _initPath: function () {
            var options = this.options;
            this.drawingElement = new d.Path({
                fill: options.fill,
                stroke: options.stroke
            });

            this.container.append(this.drawingElement);

            if (options.points) {
                this._updatePath();
            }
        },

        _pointsDiffer: function (points) {
            var currentPoints = this.options.points;
            var differ = currentPoints.length !== points.length;
            if (!differ) {
                for (var i = 0; i < points.length; i++) {
                    if (currentPoints[i].x !== points[i].x || currentPoints[i].y !== points[i].y) {
                        differ = true;
                        break;
                    }
                }
            }

            return differ;
        },

        _updatePath: function () {
            var drawingElement = this.drawingElement;
            var options = this.options;
            var points = options.points;
            var segments = [];
            var point;
            for (var i = 0; i < points.length; i++) {
                point = points[i];
                segments.push(createSegment(point.x, point.y));
            }

            drawingElement.segments.elements(segments);
        },

        options: {
            points: []
        }
    });

    deepExtend(Polyline.fn, MarkerPathMixin);

    var Image = Element.extend({
        init: function (options) {
            Element.fn.init.call(this, options);

            this._initImage();
        },

        redraw: function (options) {
            if (options) {
                if (options.source) {
                    this.drawingElement.src(options.source);
                }

                if (this._diffNumericOptions(options, [WIDTH, HEIGHT, X, Y])) {
                    this.drawingElement.rect(this._rect());
                }

                Element.fn.redraw.call(this, options);
            }
        },

        _initImage: function () {
            var options = this.options;
            var rect = this._rect();

            this.drawingElement = new d.Image(options.source, rect, {});
        },

        _rect: function () {
            var sizeOptions = sizeOptionsOrDefault(this.options);
            var origin = new g.Point(sizeOptions.x, sizeOptions.y);
            var size = new g.Size(sizeOptions.width, sizeOptions.height);

            return new g.Rect(origin, size);
        }
    });

    var Group = Element.extend({
        init: function (options) {
            this.children = [];
            Element.fn.init.call(this, options);
            this.drawingElement = new d.Group();
            this._initSize();
        },

        options: {
            autoSize: false
        },

        append: function (visual) {
            this.drawingElement.append(visual.drawingContainer());
            this.children.push(visual);
            this._childrenChange = true;
        },

        remove: function (visual) {
            if (this._remove(visual)) {
                this._childrenChange = true;
            }
        },

        _remove: function (visual) {
            var index = inArray(visual, this.children);
            if (index >= 0) {
                this.drawingElement.removeAt(index);
                this.children.splice(index, 1);
                return true;
            }
        },

        clear: function () {
            this.drawingElement.clear();
            this.children = [];
            this._childrenChange = true;
        },

        toFront: function (visuals) {
            var visual;

            for (var i = 0; i < visuals.length; i++) {
                visual = visuals[i];
                if (this._remove(visual)) {
                    this.append(visual);
                }
            }
        },
        //TO DO: add drawing group support for moving and inserting children
        toBack: function (visuals) {
            this._reorderChildren(visuals, 0);
        },

        toIndex: function (visuals, indices) {
            this._reorderChildren(visuals, indices);
        },

        _reorderChildren: function (visuals, indices) {
            var group = this.drawingElement;
            var drawingChildren = group.children.slice(0);
            var children = this.children;
            var fixedPosition = isNumber(indices);
            var i, index, toIndex, drawingElement, visual;

            for (i = 0; i < visuals.length; i++) {
                visual = visuals[i];
                drawingElement = visual.drawingContainer();

                index = inArray(visual, children);
                if (index >= 0) {
                    drawingChildren.splice(index, 1);
                    children.splice(index, 1);

                    toIndex = fixedPosition ? indices : indices[i];

                    drawingChildren.splice(toIndex, 0, drawingElement);
                    children.splice(toIndex, 0, visual);
                }
            }
            group.clear();
            group.append.apply(group, drawingChildren);
        },

        redraw: function (options) {
            if (options) {
                if (this._childrenChange) {
                    this._childrenChange = false;
                    if (!this._updateSize(options)) {
                        this._initSize();
                    }
                } else {
                    this._updateSize(options);
                }

                Element.fn.redraw.call(this, options);
            }
        },

        _boundingBox: function () {
            var children = this.children;
            var boundingBox;
            var visual, childBoundingBox;
            for (var i = 0; i < children.length; i++) {
                visual = children[i];
                if (visual.visible() && visual._includeInBBox !== false) {
                    childBoundingBox = visual.drawingContainer().bbox(null);
                    if (childBoundingBox) {
                        if (boundingBox) {
                            boundingBox = Rect.union(boundingBox, childBoundingBox);
                        } else {
                            boundingBox = childBoundingBox;
                        }
                    }
                }
            }

            return boundingBox;
        }
    });

    deepExtend(Group.fn, AutoSizeableMixin);

    var Circle = VisualBase.extend({
        init: function (options) {
            VisualBase.fn.init.call(this, options);
            this._initCircle();
            this._initSize();
        },

        redraw: function (options) {
            if (options) {
                var circleOptions = this.options;

                if (options.center) {
                    deepExtend(circleOptions, {
                        center: options.center
                    });
                    this._center.move(circleOptions.center.x, circleOptions.center.y);
                }

                if (this._diffNumericOptions(options, ["radius"])) {
                    this._circle.setRadius(circleOptions.radius);
                }

                this._updateSize(options);

                VisualBase.fn.redraw.call(this, options);
            }
        },

        _initCircle: function () {
            var options = this.options;
            var width = options.width;
            var height = options.height;
            var radius = options.radius;
            if (!defined(radius)) {
                if (!defined(width)) {
                    width = height;
                }
                if (!defined(height)) {
                    height = width;
                }
                options.radius = radius = Math.min(width, height) / 2;
            }

            var center = options.center || { x: radius, y: radius };
            this._center = new g.Point(center.x, center.y);
            this._circle = new g.Circle(this._center, radius);
            this.drawingElement = new d.Circle(this._circle, {
                fill: options.fill,
                stroke: options.stroke
            });
        }
    });
    deepExtend(Circle.fn, AutoSizeableMixin);

    var Canvas = Class.extend({
        init: function (element, options) {
            options = options || {};
            this.element = element;
            this.surface = d.Surface.create(element, options);
            if (kendo.isFunction(this.surface.translate)) {
                this.translate = this._translate;
            }

            this.drawingElement = new d.Group();
            this._viewBox = new Rect(0, 0, options.width, options.height);
            this.size(this._viewBox);
        },

        bounds: function () {
            var box = this.drawingElement.bbox();
            return new Rect(0, 0, box.width(), box.height());
        },

        size: function (size) {
            var viewBox = this._viewBox;
            if (defined(size)) {
                viewBox.width = size.width;
                viewBox.height = size.height;
                this.surface.setSize(size);
            }
            return {
                width: viewBox.width,
                height: viewBox.height
            };
        },

        _translate: function (x, y) {
            var viewBox = this._viewBox;
            if (defined(x) && defined(y)) {
                viewBox.x = x;
                viewBox.y = y;
                this.surface.translate({ x: x, y: y });
            }
            return {
                x: viewBox.x,
                y: viewBox.y
            };
        },

        draw: function () {
            this.surface.draw(this.drawingElement);
        },

        append: function (visual) {
            this.drawingElement.append(visual.drawingContainer());
            return this;
        },

        remove: function (visual) {
            this.drawingElement.remove(visual.drawingContainer());
        },

        insertBefore: function (visual, beforeVisual) {

        },

        clear: function () {
            this.drawingElement.clear();
        },

        destroy: function (clearHtml) {
            this.surface.destroy();
            if (clearHtml) {
                $(this.element).remove();
            }
        }
    });

    // Helper functions ===========================================

    function sizeOptionsOrDefault(options) {
        return {
            x: options.x || 0,
            y: options.y || 0,
            width: options.width || 0,
            height: options.height || 0
        };
    }

    function normalizeDrawingOptions(options) {
        if (options) {
            var drawingOptions = options;

            if (isString(drawingOptions)) {
                drawingOptions = {
                    color: drawingOptions
                };
            }

            if (drawingOptions.color) {
                drawingOptions.color = getColor(drawingOptions.color);
            }
            return drawingOptions;
        }
    }

    function getColor(value) {
        var color;
        if (value != TRANSPARENT) {
            color = new d.Color(value).toHex();
        } else {
            color = value;
        }
        return color;
    }

    function lineAngle(p1, p2) {
        var xDiff = p2.x - p1.x;
        var yDiff = p2.y - p1.y;
        var angle = kendo.util.deg(Math.atan2(yDiff, xDiff));
        return angle;
    }

    function diffNumericOptions(options, fields) {
        var elementOptions = this.options;
        var hasChanges = false;
        var value, field;
        for (var i = 0; i < fields.length; i++) {
            field = fields[i];
            value = options[field];
            if (isNumber(value) && elementOptions[field] !== value) {
                elementOptions[field] = value;
                hasChanges = true;
            }
        }

        return hasChanges;
    }

    function createSegment(x, y) {
        return new d.Segment(new g.Point(x, y));
    }

    // Exports ================================================================
    kendo.deepExtend(diagram, {
        init: function (element) {
            kendo.init(element, diagram.ui);
        },
        diffNumericOptions: diffNumericOptions,
        Element: Element,
        Scale: Scale,
        Translation: Translation,
        Rotation: Rotation,
        Circle: Circle,
        Group: Group,
        Rectangle: Rectangle,
        Canvas: Canvas,
        Path: Path,
        Line: Line,
        MarkerBase: MarkerBase,
        ArrowMarker: ArrowMarker,
        CircleMarker: CircleMarker,
        Polyline: Polyline,
        CompositeTransform: CompositeTransform,
        TextBlock: TextBlock,
        Image: Image,
        VisualBase: VisualBase
    });
})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var kendo = window.kendo,
            dataviz = kendo.dataviz,
            diagram = dataviz.diagram,
            Class = kendo.Class,
            Group = diagram.Group,
            TextBlock = diagram.TextBlock,
            Rect = diagram.Rect,
            Rectangle = diagram.Rectangle,
            Utils = diagram.Utils,
            isUndefined = Utils.isUndefined,
            Point = diagram.Point,
            Circle = diagram.Circle,
            Path = diagram.Path,
            Ticker = diagram.Ticker,
            deepExtend = kendo.deepExtend,
            Movable = kendo.ui.Movable,
            browser = kendo.support.browser,
            defined = kendo.util.defined,

            proxy = $.proxy;
    // Constants ==============================================================
    var Cursors = {
        arrow: "default",
        grip: "pointer",
        cross: "pointer",
        add: "pointer",
        move: "move",
        select: "pointer",
        south: "s-resize",
        east: "e-resize",
        west: "w-resize",
        north: "n-resize",
        rowresize: "row-resize",
        colresize: "col-resize"
    },
            HITTESTDISTANCE = 10,
            AUTO = "Auto",
            TOP = "Top",
            RIGHT = "Right",
            LEFT = "Left",
            BOTTOM = "Bottom",
            DEFAULTCONNECTORNAMES = [TOP, RIGHT, BOTTOM, LEFT, AUTO],
            ITEMROTATE = "itemRotate",
            ITEMBOUNDSCHANGE = "itemBoundsChange",
            ZOOMSTART = "zoomStart",
            ZOOMEND = "zoomEnd",
            SCROLL_MIN = -20000,
            SCROLL_MAX = 20000,
            FRICTION = 0.90,
            FRICTION_MOBILE = 0.93,
            VELOCITY_MULTIPLIER = 5,
            TRANSPARENT = "transparent",
            PAN = "pan";

    diagram.Cursors = Cursors;

    function selectSingle(item, meta) {
        if (item.isSelected) {
            if (meta.ctrlKey) {
                item.select(false);
            }
        } else {
            item.diagram.select(item, { addToSelection: meta.ctrlKey });
        }
    }

    var PositionAdapter = kendo.Class.extend({
        init: function (layoutState) {
            this.layoutState = layoutState;
            this.diagram = layoutState.diagram;
        },
        initState: function () {
            this.froms = [];
            this.tos = [];
            this.subjects = [];
            function pusher(id, bounds) {
                var shape = this.diagram.getShapeById(id);
                if (shape) {
                    this.subjects.push(shape);
                    this.froms.push(shape.bounds().topLeft());
                    this.tos.push(bounds.topLeft());
                }
            }

            this.layoutState.nodeMap.forEach(pusher, this);
        },
        update: function (tick) {
            if (this.subjects.length <= 0) {
                return;
            }
            for (var i = 0; i < this.subjects.length; i++) {
                //todo: define a Lerp function instead
                this.subjects[i].position(
                        new Point(this.froms[i].x + (this.tos[i].x - this.froms[i].x) * tick, this.froms[i].y + (this.tos[i].y - this.froms[i].y) * tick)
                    );
            }
        }
    });

    var LayoutUndoUnit = Class.extend({
        init: function (initialState, finalState, animate) {
            if (isUndefined(animate)) {
                this.animate = false;
            }
            else {
                this.animate = animate;
            }
            this._initialState = initialState;
            this._finalState = finalState;
            this.title = "Diagram layout";
        },
        undo: function () {
            this.setState(this._initialState);
        },
        redo: function () {
            this.setState(this._finalState);
        },
        setState: function (state) {
            var diagram = state.diagram;
            if (this.animate) {
                state.linkMap.forEach(
                        function (id, points) {
                            var conn = diagram.getShapeById(id);
                            conn.visible(false);
                            if (conn) {
                                conn.points(points);
                            }
                        }
                    );
                var ticker = new Ticker();
                ticker.addAdapter(new PositionAdapter(state));
                ticker.onComplete(function () {
                    state.linkMap.forEach(
                            function (id) {
                                var conn = diagram.getShapeById(id);
                                conn.visible(true);
                            }
                        );
                });
                ticker.play();
            }
            else {
                state.nodeMap.forEach(function (id, bounds) {
                    var shape = diagram.getShapeById(id);
                    if (shape) {
                        shape.position(bounds.topLeft());
                    }
                });
                state.linkMap.forEach(
                        function (id, points) {
                            var conn = diagram.getShapeById(id);
                            if (conn) {
                                conn.points(points);
                            }
                        }
                    );
            }
        }
    });

    var CompositeUnit = Class.extend({
        init: function (unit) {
            this.units = [];
            this.title = "Composite unit";
            if (unit !== undefined) {
                this.units.push(unit);
            }
        },
        add: function (undoUnit) {
            this.units.push(undoUnit);
        },
        undo: function () {
            for (var i = 0; i < this.units.length; i++) {
                this.units[i].undo();
            }
        },
        redo: function () {
            for (var i = 0; i < this.units.length; i++) {
                this.units[i].redo();
            }
        }
    });

    var ConnectionEditUnit = Class.extend({
        init: function (item, redoSource, redoTarget) {
            this.item = item;
            this._redoSource = redoSource;
            this._redoTarget = redoTarget;
            this._undoSource = item.source();
            this._undoTarget = item.target();
            this.title = "Connection Editing";
        },
        undo: function () {
            if (this._undoSource !== undefined) {
                this.item.source(this._undoSource, false);
            }

            if (this._undoTarget !== undefined) {
                this.item.target(this._undoTarget, false);
            }

            this.item.updateModel(this._undoSource, this._undoTarget);
        },
        redo: function () {
            if (this._redoSource !== undefined) {
                this.item.source(this._redoSource, false);
            }

            if (this._redoTarget !== undefined) {
                this.item.target(this._redoTarget, false);
            }

            this.item.updateModel(this._undoSource, this._undoTarget);
        }
    });

    var ConnectionEditUndoUnit = Class.extend({
        init: function (item, undoSource, undoTarget) {
            this.item = item;
            this._undoSource = undoSource;
            this._undoTarget = undoTarget;
            this._redoSource = item.source();
            this._redoTarget = item.target();
            this.title = "Connection Editing";
        },
        undo: function () {
            this.item.source(this._undoSource, false);
            this.item.target(this._undoTarget, false);
            this.item.updateModel(this._undoSource, this._undoTarget);
        },
        redo: function () {
            this.item.source(this._redoSource, false);
            this.item.target(this._redoTarget, false);
            this.item.updateModel(this._undoSource, this._undoTarget);
        }
    });

    var DeleteConnectionUnit = Class.extend({
        init: function (connection) {
            this.connection = connection;
            this.diagram = connection.diagram;
            this.targetConnector = connection.targetConnector;
            this.title = "Delete connection";
        },
        undo: function () {
            this.diagram._addConnection(this.connection, false);
        },
        redo: function () {
            this.diagram._remove(this.connection, false);
        }
    });

    var DeleteShapeUnit = Class.extend({
        init: function (shape) {
            this.shape = shape;
            this.diagram = shape.diagram;
            this.title = "Deletion";
        },
        undo: function () {
            this.diagram._addShape(this.shape, { undoable: false });
            this.shape.select(false);
        },
        redo: function () {
            this.shape.select(false);
            this.diagram._remove(this.shape, false);
        }
    });
    /**
    * Holds the undoredo state when performing a rotation, translation or scaling. The adorner is optional.
    * @type {*}
    */
    var TransformUnit = Class.extend({
        init: function (shapes, undoStates, adorner) {
            this.shapes = shapes;
            this.undoStates = undoStates;
            this.title = "Transformation";
            this.redoStates = [];
            this.adorner = adorner;
            for (var i = 0; i < this.shapes.length; i++) {
                var shape = this.shapes[i];
                this.redoStates.push(shape.bounds());
            }
        },
        undo: function () {
            for (var i = 0; i < this.shapes.length; i++) {
                var shape = this.shapes[i];
                shape.bounds(this.undoStates[i]);
                if (shape.hasOwnProperty("layout")) {
                    shape.layout(shape, this.redoStates[i], this.undoStates[i]);
                }
            }
            if (this.adorner) {
                this.adorner.refreshBounds();
                this.adorner.refresh();
            }
        },
        redo: function () {
            for (var i = 0; i < this.shapes.length; i++) {
                var shape = this.shapes[i];
                shape.bounds(this.redoStates[i]);
                // the 'layout' property, if implemented, lets the shape itself work out what to do with the new bounds
                if (shape.hasOwnProperty("layout")) {
                    shape.layout(shape, this.undoStates[i], this.redoStates[i]);
                }
            }

            if (this.adorner) {
                this.adorner.refreshBounds();
                this.adorner.refresh();
            }
        }
    });

    var AddConnectionUnit = Class.extend({
        init: function (connection, diagram) {
            this.connection = connection;
            this.diagram = diagram;
            this.title = "New connection";
        },

        undo: function () {
            this.diagram._remove(this.connection, false);
        },

        redo: function () {
            this.diagram._addConnection(this.connection, false);
        }
    });

    var AddShapeUnit = Class.extend({
        init: function (shape, diagram) {
            this.shape = shape;
            this.diagram = diagram;
            this.title = "New shape";
        },

        undo: function () {
            this.diagram.deselect();
            this.diagram._remove(this.shape, false);
        },

        redo: function () {
            this.diagram._addShape(this.shape, false);
        }
    });

    var PanUndoUnit = Class.extend({
        init: function (initialPosition, finalPosition, diagram) {
            this.initial = initialPosition;
            this.finalPos = finalPosition;
            this.diagram = diagram;
            this.title = "Pan Unit";
        },
        undo: function () {
            this.diagram.pan(this.initial);
        },
        redo: function () {
            this.diagram.pan(this.finalPos);
        }
    });

    var RotateUnit = Class.extend({
        init: function (adorner, shapes, undoRotates) {
            this.shapes = shapes;
            this.undoRotates = undoRotates;
            this.title = "Rotation";
            this.redoRotates = [];
            this.redoAngle = adorner._angle;
            this.adorner = adorner;
            this.center = adorner._innerBounds.center();
            for (var i = 0; i < this.shapes.length; i++) {
                var shape = this.shapes[i];
                this.redoRotates.push(shape.rotate().angle);
            }
        },
        undo: function () {
            var i, shape;
            for (i = 0; i < this.shapes.length; i++) {
                shape = this.shapes[i];
                shape.rotate(this.undoRotates[i], this.center, false);
                if (shape.hasOwnProperty("layout")) {
                    shape.layout(shape);
                }
            }
            if (this.adorner) {
                this.adorner._initialize();
                this.adorner.refresh();
            }
        },
        redo: function () {
            var i, shape;
            for (i = 0; i < this.shapes.length; i++) {
                shape = this.shapes[i];
                shape.rotate(this.redoRotates[i], this.center, false);
                if (shape.hasOwnProperty("layout")) {
                    shape.layout(shape);
                }
            }
            if (this.adorner) {
                this.adorner._initialize();
                this.adorner.refresh();
            }
        }
    });

    var ToFrontUnit = Class.extend({
        init: function (diagram, items, initialIndices) {
            this.diagram = diagram;
            this.indices = initialIndices;
            this.items = items;
            this.title = "Rotate Unit";
        },
        undo: function () {
            this.diagram._toIndex(this.items, this.indices);
        },
        redo: function () {
            this.diagram.toFront(this.items, false);
        }
    });

    var ToBackUnit = Class.extend({
        init: function (diagram, items, initialIndices) {
            this.diagram = diagram;
            this.indices = initialIndices;
            this.items = items;
            this.title = "Rotate Unit";
        },
        undo: function () {
            this.diagram._toIndex(this.items, this.indices);
        },
        redo: function () {
            this.diagram.toBack(this.items, false);
        }
    });

    /**
    * Undo-redo service.
    */
    var UndoRedoService = Class.extend({
        init: function () {
            this.stack = [];
            this.index = 0;
            this.capacity = 100;
        },

        /**
        * Starts the collection of units. Add those with
        * the addCompositeItem method and call commit. Or cancel to forget about it.
        */
        begin: function () {
            this.composite = new CompositeUnit();
        },

        /**
        * Cancels the collection process of unit started with 'begin'.
        */
        cancel: function () {
            this.composite = undefined;
        },

        /**
        * Commits a batch of units.
        */
        commit: function () {
            if (this.composite.units.length > 0) {
                this._restart(this.composite);
            }
            this.composite = undefined;
        },

        /**
        * Adds a unit as part of the begin-commit batch.
        * @param undoUnit
        */
        addCompositeItem: function (undoUnit) {
            if (this.composite) {
                this.composite.add(undoUnit);
            } else {
                this.add(undoUnit);
            }
        },

        /**
        * Standard addition of a unit. See also the batch version; begin-addCompositeUnit-commit methods.
        * @param undoUnit The unit to be added.
        * @param execute If false, the unit will be added but not executed.
        */
        add: function (undoUnit, execute) {
            this._restart(undoUnit, execute);
        },

        /**
        * Returns the number of undoable unit in the stack.
        * @returns {Number}
        */
        count: function () {
            return this.stack.length;
        },

        /**
        * Rollback of the unit on top of the stack.
        */
        undo: function () {
            if (this.index > 0) {
                this.index--;
                this.stack[this.index].undo();
            }
        },

        /**
        * Redo of the last undone action.
        */
        redo: function () {
            if (this.stack.length > 0 && this.index < this.stack.length) {
                this.stack[this.index].redo();
                this.index++;
            }
        },

        _restart: function (composite, execute) {
            // throw away anything beyond this point if this is a new branch
            this.stack.splice(this.index, this.stack.length - this.index);
            this.stack.push(composite);
            if (isUndefined(execute) || (execute && (execute === true))) {
                this.redo();
            } else {
                this.index++;
            }
            // check the capacity
            if (this.stack.length > this.capacity) {
                this.stack.splice(0, this.stack.length - this.capacity);
                this.index = this.capacity; //points to the end of the stack
            }
        },

        /**
        * Clears the stack.
        */
        clear: function () {
            this.stack = [];
            this.index = 0;
        }
    });

    // Tools =========================================

    var EmptyTool = Class.extend({
        init: function (toolService) {
            this.toolService = toolService;
        },
        start: function () {
        },
        move: function () {
        },
        end: function () {
        },
        tryActivate: function (p, meta) {
            return false;
        },
        getCursor: function () {
            return Cursors.arrow;
        }
    });

    function noMeta(meta) {
        return meta.ctrlKey === false && meta.altKey === false && meta.shiftKey === false;
    }

    function tryActivateSelection(options, meta) {
        var enabled = options !== false;

        if (options.key && options.key != "none") {
            enabled = meta[options.key + "Key"];
        }
        return enabled;
    }

    var ScrollerTool = EmptyTool.extend({
        init: function (toolService) {
            var tool = this;
            var friction = kendo.support.mobileOS ? FRICTION_MOBILE : FRICTION;
            EmptyTool.fn.init.call(tool, toolService);

            var diagram = tool.toolService.diagram,
                    canvas = diagram.canvas;

            var scroller = diagram.scroller = tool.scroller = $(diagram.scrollable).kendoMobileScroller({
                friction: friction,
                velocityMultiplier: VELOCITY_MULTIPLIER,
                mousewheelScrolling: false,
                zoom: false,
                scroll: proxy(tool._move, tool)
            }).data("kendoMobileScroller");

            if (canvas.translate) {
                tool.movableCanvas = new Movable(canvas.element);
            }

            var virtualScroll = function (dimension, min, max) {
                dimension.makeVirtual();
                dimension.virtualSize(min || SCROLL_MIN, max || SCROLL_MAX);
            };

            virtualScroll(scroller.dimensions.x);
            virtualScroll(scroller.dimensions.y);
            scroller.disable();
        },

        tryActivate: function (p, meta) {
            var toolService = this.toolService;
            var options = toolService.diagram.options.pannable;
            var enabled = meta.ctrlKey;

            if (defined(options.key)) {
                if (!options.key || options.key == "none") {
                    enabled = noMeta(meta);
                } else {
                    enabled = meta[options.key + "Key"] && !(meta.ctrlKey && defined(toolService.hoveredItem));
                }
            }

            return options !== false && enabled && !defined(toolService.hoveredAdorner) && !defined(toolService._hoveredConnector);
        },

        start: function () {
            this.scroller.enable();
        },
        move: function () {
        }, //the tool itself should not handle the scrolling. Let kendo scroller take care of this part. Check _move
        _move: function (args) {
            var tool = this,
                    diagram = tool.toolService.diagram,
                    canvas = diagram.canvas,
                    scrollPos = new Point(args.scrollLeft, args.scrollTop);

            if (canvas.translate) {
                diagram._storePan(scrollPos.times(-1));
                tool.movableCanvas.moveTo(scrollPos);
                canvas.translate(scrollPos.x, scrollPos.y);
            } else {
                scrollPos = scrollPos.plus(diagram._pan.times(-1));
            }

            diagram.trigger(PAN, { pan: scrollPos });
        },
        end: function () {
            this.scroller.disable();
        },
        getCursor: function () {
            return Cursors.move;
        }
    });

    /**
    * The tool handling the transformations via the adorner.
    * @type {*}
    */
    var PointerTool = Class.extend({
        init: function (toolService) {
            this.toolService = toolService;
        },
        tryActivate: function (p, meta) {
            return true; // the pointer tool is last and handles all others requests.
        },
        start: function (p, meta) {
            var toolService = this.toolService,
                    diagram = toolService.diagram,
                    hoveredItem = toolService.hoveredItem,
                    selectable = diagram.options.selectable;

            if (hoveredItem) {
                if (tryActivateSelection(selectable, meta)) {
                    selectSingle(hoveredItem, meta);
                }
                if (hoveredItem.adorner) { //connection
                    this.adorner = hoveredItem.adorner;
                    this.handle = this.adorner._hitTest(p);
                }
            }

            if (!this.handle) {
                this.handle = diagram._resizingAdorner._hitTest(p);
                if (this.handle) {
                    this.adorner = diagram._resizingAdorner;
                }
            }

            if (this.adorner) {
                this.adorner.start(p);
            }
        },
        move: function (p) {
            var that = this;
            if (this.adorner) {
                this.adorner.move(that.handle, p);
            }
        },
        end: function (p, meta) {
            var diagram = this.toolService.diagram,
                    service = this.toolService,
                    unit;

            if (this.adorner) {
                unit = this.adorner.stop();
                if (unit) {
                    diagram.undoRedoService.add(unit, false);
                }
            }
            if (service.hoveredItem) {
                this.toolService.triggerClick({ item: service.hoveredItem, point: p, meta: meta });
            }
            this.adorner = undefined;
            this.handle = undefined;
        },
        getCursor: function (p) {
            return this.toolService.hoveredItem ? this.toolService.hoveredItem._getCursor(p) : Cursors.arrow;
        }
    });

    var SelectionTool = Class.extend({
        init: function (toolService) {
            this.toolService = toolService;
        },
        tryActivate: function (p, meta) {
            var toolService = this.toolService;
            var enabled = tryActivateSelection(toolService.diagram.options.selectable, meta);

            return enabled && !defined(toolService.hoveredItem) && !defined(toolService.hoveredAdorner);
        },
        start: function (p) {
            var diagram = this.toolService.diagram;
            diagram.deselect();
            diagram.selector.start(p);
        },
        move: function (p) {
            var diagram = this.toolService.diagram;
            diagram.selector.move(p);
        },
        end: function (p, meta) {
            var diagram = this.toolService.diagram, hoveredItem = this.toolService.hoveredItem;
            var rect = diagram.selector.bounds();
            if ((!hoveredItem || !hoveredItem.isSelected) && !meta.ctrlKey) {
                diagram.deselect();
            }
            if (!rect.isEmpty()) {
                diagram.selectArea(rect);
            }
            diagram.selector.end();
        },
        getCursor: function () {
            return Cursors.arrow;
        }
    });

    var ConnectionTool = Class.extend({
        init: function (toolService) {
            this.toolService = toolService;
            this.type = "ConnectionTool";
        },
        tryActivate: function (p, meta) {
            return this.toolService._hoveredConnector;
        },
        start: function (p, meta) {
            var diagram = this.toolService.diagram,
                    connector = this.toolService._hoveredConnector,
                    connection = diagram.connect(connector._c, p);

            this.toolService._connectionManipulation(connection, connector._c.shape, true);
            this.toolService._removeHover();
            selectSingle(this.toolService.activeConnection, meta);
        },
        move: function (p) {
            this.toolService.activeConnection.target(p);
            return true;
        },
        end: function () {
            var nc = this.toolService.activeConnection,
                    hi = this.toolService.hoveredItem,
                    connector = this.toolService._hoveredConnector;

            if (connector && connector._c != nc.sourceConnector) {
                nc.target(connector._c);
            } else if (hi) {
                nc.target(hi);
            }

            nc.updateModel();

            this.toolService._connectionManipulation();
        },
        getCursor: function () {
            return Cursors.arrow;
        }
    });

    var ConnectionEditTool = Class.extend({
        init: function (toolService) {
            this.toolService = toolService;
            this.type = "ConnectionTool";
        },

        tryActivate: function (p, meta) {
            var toolService = this.toolService,
                    diagram = toolService.diagram,
                    selectable = diagram.options.selectable,
                    item = toolService.hoveredItem,
                    isActive = tryActivateSelection(selectable, meta) && item && item.path &&
                        !(item.isSelected && meta.ctrlKey); // means it is connection
            if (isActive) {
                this._c = item;
            }
            return isActive;
        },
        start: function (p, meta) {
            selectSingle(this._c, meta);
            this.handle = this._c.adorner._hitTest(p);
            this._c.adorner.start(p);
        },
        move: function (p) {
            this._c.adorner.move(this.handle, p);
            return true;
        },
        end: function (p, meta) {
            this.toolService.triggerClick({ item: this._c, point: p, meta: meta });
            var unit = this._c.adorner.stop(p);
            this.toolService.diagram.undoRedoService.add(unit, false);
        },
        getCursor: function () {
            return Cursors.move;
        }
    });

    function testKey(key, str) {
        return str.charCodeAt(0) == key || str.toUpperCase().charCodeAt(0) == key;
    }

    /**
    * The service managing the tools.
    * @type {*}
    */
    var ToolService = Class.extend({
        init: function (diagram) {
            this.diagram = diagram;
            this.tools = [
                    new ScrollerTool(this),
                    new ConnectionEditTool(this),
                    new ConnectionTool(this),
                    new SelectionTool(this),
                    new PointerTool(this)
                ]; // the order matters.

            this.activeTool = undefined;
        },

        start: function (p, meta) {
            meta = deepExtend({}, meta);
            if (this.activeTool) {
                this.activeTool.end(p, meta);
            }
            this._updateHoveredItem(p);
            this._activateTool(p, meta);
            this.activeTool.start(p, meta);
            this._updateCursor(p);
            this.diagram.focus();
            this.startPoint = p;
            return true;
        },

        move: function (p, meta) {
            meta = deepExtend({}, meta);
            var updateHovered = true;
            if (this.activeTool) {
                updateHovered = this.activeTool.move(p, meta);
            }
            if (updateHovered) {
                this._updateHoveredItem(p);
            }
            this._updateCursor(p);
            return true;
        },

        end: function (p, meta) {
            meta = deepExtend({}, meta);
            if (this.activeTool) {
                this.activeTool.end(p, meta);
            }
            this.activeTool = undefined;
            this._updateCursor(p);
            return true;
        },

        keyDown: function (key, meta) {
            var diagram = this.diagram;
            meta = deepExtend({ ctrlKey: false, metaKey: false, altKey: false }, meta);
            if ((meta.ctrlKey || meta.metaKey) && !meta.altKey) {// ctrl or option
                if (testKey(key, "a")) {// A: select all
                    diagram.selectAll();
                    diagram._destroyToolBar();
                    return true;
                } else if (testKey(key, "z")) {// Z: undo
                    diagram.undo();
                    diagram._destroyToolBar();
                    return true;
                } else if (testKey(key, "y")) {// y: redo
                    diagram.redo();
                    diagram._destroyToolBar();
                    return true;
                } else if (testKey(key, "c")) {
                    diagram.copy();
                    diagram._destroyToolBar();
                } else if (testKey(key, "x")) {
                    diagram.cut();
                    diagram._destroyToolBar();
                } else if (testKey(key, "v")) {
                    diagram.paste();
                    diagram._destroyToolBar();
                } else if (testKey(key, "l")) {
                    diagram.layout();
                    diagram._destroyToolBar();
                } else if (testKey(key, "d")) {
                    diagram._destroyToolBar();
                    diagram.copy();
                    diagram.paste();
                }
            } else if (key === 46 || key === 8) {// del: deletion
                diagram.remove(diagram.select(), true);
                diagram._destroyToolBar();
                return true;
            } else if (key === 27) {// ESC: stop any action
                this._discardNewConnection();
                diagram.deselect();
                diagram._destroyToolBar();
                return true;
            }

        },
        wheel: function (p, meta) {
            var diagram = this.diagram,
                    delta = meta.delta,
                    z = diagram.zoom(),
                    options = diagram.options,
                    zoomRate = options.zoomRate,
                    zoomOptions = { point: p, meta: meta, zoom: z };

            diagram.trigger(ZOOMSTART, zoomOptions);

            if (delta < 0) {
                z += zoomRate;
            } else {
                z -= zoomRate;
            }

            z = kendo.dataviz.round(Math.max(options.minZoom, Math.min(options.maxZoom, z)), 2);
            zoomOptions.zoom = z;

            diagram.zoom(z, zoomOptions);
            diagram.trigger(ZOOMEND, zoomOptions);

            return true;
        },
        setTool: function (tool, index) {
            tool.toolService = this;
            this.tools[index] = tool;
        },
        triggerClick: function (data) {
            if (this.startPoint.equals(data.point)) {
                this.diagram.trigger("click", data);
            }

        },
        _discardNewConnection: function () {
            if (this.newConnection) {
                this.diagram.remove(this.newConnection);
                this.newConnection = undefined;
            }
        },
        _activateTool: function (p, meta) {
            for (var i = 0; i < this.tools.length; i++) {
                var tool = this.tools[i];
                if (tool.tryActivate(p, meta)) {
                    this.activeTool = tool;
                    break; // activating the first available tool in the loop.
                }
            }
        },
        _updateCursor: function (p) {
            var element = this.diagram.element;
            var cursor = this.activeTool ? this.activeTool.getCursor(p) : (this.hoveredAdorner ? this.hoveredAdorner._getCursor(p) : (this.hoveredItem ? this.hoveredItem._getCursor(p) : Cursors.arrow));

            element.css({ cursor: cursor });
            // workaround for IE 7 issue in which the elements overflow the container after setting cursor
            if (browser.msie && browser.version == 7) {
                element[0].style.cssText = element[0].style.cssText;
            }
        },
        _connectionManipulation: function (connection, disabledShape, isNew) {
            this.activeConnection = connection;
            this.disabledShape = disabledShape;
            if (isNew) {
                this.newConnection = this.activeConnection;
            } else {
                this.newConnection = undefined;
            }
        },
        _updateHoveredItem: function (p) {
            var hit = this._hitTest(p);

            if (hit != this.hoveredItem && (!this.disabledShape || hit != this.disabledShape)) {
                if (this.hoveredItem) {
                    this.hoveredItem._hover(false);
                }

                if (hit && hit.options.enable) {
                    this.hoveredItem = hit; // Shape, connection or connector
                    this.hoveredItem._hover(true);
                } else {
                    this.hoveredItem = undefined;
                }
            }
        },
        _removeHover: function () {
            if (this.hoveredItem) {
                this.hoveredItem._hover(false);
                this.hoveredItem = undefined;
            }
        },
        _hitTest: function (point) {
            var hit, d = this.diagram, item, i;

            // connectors
            if (this._hoveredConnector) {
                this._hoveredConnector._hover(false);
                this._hoveredConnector = undefined;
            }
            if (d._connectorsAdorner._visible) {
                hit = d._connectorsAdorner._hitTest(point);
                if (hit) {
                    return hit;
                }
            }

            hit = this.diagram._resizingAdorner._hitTest(point);
            if (hit) {
                this.hoveredAdorner = d._resizingAdorner;
                if (hit.x !== 0 && hit.y !== 0) { // hit testing for resizers or rotator, otherwise if (0,0) than pass through.
                    return;
                }
                hit = undefined;
            } else {
                this.hoveredAdorner = undefined;
            }

            if (!this.activeTool || this.activeTool.type !== "ConnectionTool") {
                var selectedConnections = []; // only the connections should have higher presence because the connection edit point is on top of connector.
                // TODO: This should be reworked. The connection adorner should be one for all selected connections and should be hit tested prior the connections and shapes itself.
                for (i = 0; i < d._selectedItems.length; i++) {
                    item = d._selectedItems[i];
                    if (item instanceof diagram.Connection) {
                        selectedConnections.push(item);
                    }
                }
                hit = this._hitTestItems(selectedConnections, point);
            }
            // Shapes | Connectors
            return hit || this._hitTestItems(d.shapes, point) || this._hitTestItems(d.connections, point);
        },
        _hitTestItems: function (array, point) {
            var i, item, hit;
            for (i = array.length - 1; i >= 0; i--) {
                item = array[i];
                hit = item._hitTest(point);
                if (hit) {
                    return hit;
                }
            }
        }
    });

    // Routing =========================================

    /**
    * Base class for connection routers.
    */
    var ConnectionRouterBase = kendo.Class.extend({
        init: function () {
        }
        /*route: function (connection) {
        },
        hitTest: function (p) {

        },
        getBounds: function () {

        }*/
    });

    /**
    * Base class for polyline and cascading routing.
    */
    var LinearConnectionRouter = ConnectionRouterBase.extend({
        init: function (connection) {
            var that = this;
            ConnectionRouterBase.fn.init.call(that);
            this.connection = connection;
        },
        /**
        * Hit testing for polyline paths.
        */
        hitTest: function (p) {
            var rec = this.getBounds().inflate(10);
            if (!rec.contains(p)) {
                return false;
            }
            return diagram.Geometry.distanceToPolyline(p, this.connection.allPoints()) < HITTESTDISTANCE;
        },

        /**
        * Bounds of a polyline.
        * @returns {kendo.dataviz.diagram.Rect}
        */
        getBounds: function () {
            var points = this.connection.allPoints(),
                    s = points[0],
                    e = points[points.length - 1],
                    right = Math.max(s.x, e.x),
                    left = Math.min(s.x, e.x),
                    top = Math.min(s.y, e.y),
                    bottom = Math.max(s.y, e.y);

            for (var i = 1; i < points.length - 1; ++i) {
                right = Math.max(right, points[i].x);
                left = Math.min(left, points[i].x);
                top = Math.min(top, points[i].y);
                bottom = Math.max(bottom, points[i].y);
            }

            return new Rect(left, top, right - left, bottom - top);
        }
    });

    /**
    * A simple poly-linear routing which does not alter the intermediate points.
    * Does hold the underlying hit, bounds....logic.
    * @type {*|Object|void|extend|Zepto.extend|b.extend}
    */
    var PolylineRouter = LinearConnectionRouter.extend({
        init: function (connection) {
            var that = this;
            LinearConnectionRouter.fn.init.call(that);
            this.connection = connection;
        },
        route: function () {
            // just keep the points as is
        }
    });

    var CascadingRouter = LinearConnectionRouter.extend({
        init: function (connection) {
            var that = this;
            LinearConnectionRouter.fn.init.call(that);
            this.connection = connection;
        },
        route: function () {
            var link = this.connection;
            var start = this.connection.sourcePoint();
            var end = this.connection.targetPoint(),
                    points = [start, start, end, end],
                    deltaX = end.x - start.x, // can be negative
                    deltaY = end.y - start.y,
                    l = points.length,
                    shiftX,
                    shiftY,
                    sourceConnectorName = null,
                    targetConnectorName = null;

            if (Utils.isDefined(link._resolvedSourceConnector)) {
                sourceConnectorName = link._resolvedSourceConnector.options.name;
            }
            if (Utils.isDefined(link._resolvedTargetConnector)) {
                targetConnectorName = link._resolvedTargetConnector.options.name;
            }
            function startHorizontal() {
                if (sourceConnectorName !== null) {
                    if (sourceConnectorName === RIGHT || sourceConnectorName === LEFT) {
                        return true;
                    }
                    if (sourceConnectorName === TOP || sourceConnectorName === BOTTOM) {
                        return false;
                    }
                }
                //fallback for custom connectors
                return Math.abs(start.x - end.x) > Math.abs(start.y - end.y);
            }

            if (sourceConnectorName !== null && targetConnectorName !== null && Utils.contains(DEFAULTCONNECTORNAMES, sourceConnectorName) && Utils.contains(DEFAULTCONNECTORNAMES, targetConnectorName)) {
                // custom routing for the default connectors
                if (sourceConnectorName === TOP || sourceConnectorName == BOTTOM) {
                    if (targetConnectorName == TOP || targetConnectorName == BOTTOM) {
                        this.connection.points([new Point(start.x, start.y + deltaY / 2), new Point(end.x, start.y + deltaY / 2)]);
                    } else {
                        this.connection.points([new Point(start.x, start.y + deltaY)]);
                    }
                } else { // LEFT or RIGHT
                    if (targetConnectorName == LEFT || targetConnectorName == RIGHT) {
                        this.connection.points([new Point(start.x + deltaX / 2, start.y), new Point(start.x + deltaX / 2, start.y + deltaY)]);
                    } else {
                        this.connection.points([new Point(end.x, start.y)]);
                    }
                }

            }
            else { // general case for custom and floating connectors
                this.connection.cascadeStartHorizontal = startHorizontal(this.connection);

                // note that this is more generic than needed for only two intermediate points.
                for (var k = 1; k < l - 1; ++k) {
                    if (link.cascadeStartHorizontal) {
                        if (k % 2 !== 0) {
                            shiftX = deltaX / (l / 2);
                            shiftY = 0;
                        }
                        else {
                            shiftX = 0;
                            shiftY = deltaY / ((l - 1) / 2);
                        }
                    }
                    else {
                        if (k % 2 !== 0) {
                            shiftX = 0;
                            shiftY = deltaY / (l / 2);
                        }
                        else {
                            shiftX = deltaX / ((l - 1) / 2);
                            shiftY = 0;
                        }
                    }
                    points[k] = new Point(points[k - 1].x + shiftX, points[k - 1].y + shiftY);
                }
                // need to fix the wrong 1.5 factor of the last intermediate point
                k--;
                if ((link.cascadeStartHorizontal && (k % 2 !== 0)) || (!link.cascadeStartHorizontal && (k % 2 === 0))) {
                    points[l - 2] = new Point(points[l - 1].x, points[l - 2].y);
                }
                else {
                    points[l - 2] = new Point(points[l - 2].x, points[l - 1].y);
                }

                this.connection.points([points[1], points[2]]);
            }
        }

    });

    // Adorners =========================================

    var AdornerBase = Class.extend({
        init: function (diagram, options) {
            var that = this;
            that.diagram = diagram;
            that.options = deepExtend({}, that.options, options);
            that.visual = new Group();
            that.diagram._adorners.push(that);
        },
        refresh: function () {

        }
    });

    var ConnectionEditAdorner = AdornerBase.extend({
        init: function (connection, options) {
            var that = this, diagram;
            that.connection = connection;
            diagram = that.connection.diagram;
            that._ts = diagram.toolService;
            AdornerBase.fn.init.call(that, diagram, options);
            var sp = that.connection.sourcePoint();
            var tp = that.connection.targetPoint();
            that.spVisual = new Circle(deepExtend(that.options.handles, { center: sp }));
            that.epVisual = new Circle(deepExtend(that.options.handles, { center: tp }));
            that.visual.append(that.spVisual);
            that.visual.append(that.epVisual);
        },

        options: {
            handles: {}
        },

        _getCursor: function () {
            return Cursors.move;
        },

        start: function (p) {
            this.handle = this._hitTest(p);
            this.startPoint = p;
            this._initialSource = this.connection.source();
            this._initialTarget = this.connection.target();
            switch (this.handle) {
                case -1:
                    if (this.connection.targetConnector) {
                        this._ts._connectionManipulation(this.connection, this.connection.targetConnector.shape);
                    }
                    break;
                case 1:
                    if (this.connection.sourceConnector) {
                        this._ts._connectionManipulation(this.connection, this.connection.sourceConnector.shape);
                    }
                    break;
            }
        },

        move: function (handle, p) {
            switch (handle) {
                case -1:
                    this.connection.source(p);
                    break;
                case 1:
                    this.connection.target(p);
                    break;
                default:
                    var delta = p.minus(this.startPoint);
                    this.startPoint = p;
                    if (!this.connection.sourceConnector) {
                        this.connection.source(this.connection.sourcePoint().plus(delta));
                    }
                    if (!this.connection.targetConnector) {
                        this.connection.target(this.connection.targetPoint().plus(delta));
                    }
                    break;
            }
            this.refresh();
            return true;
        },

        stop: function (p) {
            var ts = this.diagram.toolService, item = ts.hoveredItem, target;
            if (ts._hoveredConnector) {
                target = ts._hoveredConnector._c;
            } else if (item && item instanceof diagram.Shape) {
                target = item;
            } else {
                target = p;
            }

            if (this.handle === -1) {
                this.connection.source(target);
            } else if (this.handle === 1) {
                this.connection.target(target);
            }

            if (this.handle) {
                this.connection.updateModel();
            }

            this.handle = undefined;
            this._ts._connectionManipulation();
            return new ConnectionEditUndoUnit(this.connection, this._initialSource, this._initialTarget);
        },

        _hitTest: function (p) {
            var sp = this.connection.sourcePoint(),
                    tp = this.connection.targetPoint(),
                    rx = this.options.handles.width / 2,
                    ry = this.options.handles.height / 2,
                    sb = new Rect(sp.x, sp.y).inflate(rx, ry),
                    tb = new Rect(tp.x, tp.y).inflate(rx, ry);

            return sb.contains(p) ? -1 : (tb.contains(p) ? 1 : 0);
        },

        refresh: function () {
            this.spVisual.redraw({ center: this.diagram.modelToLayer(this.connection.sourcePoint()) });
            this.epVisual.redraw({ center: this.diagram.modelToLayer(this.connection.targetPoint()) });
        }
    });

    var ConnectorsAdorner = AdornerBase.extend({
        init: function (diagram, options) {
            var that = this;
            AdornerBase.fn.init.call(that, diagram, options);
            that._refreshHandler = function (e) {
                if (e.item == that.shape) {
                    that.refresh();
                }
            };
        },

        show: function (shape) {
            var that = this, len, i, ctr;
            that._visible = true;
            that.shape = shape;
            that.diagram.bind(ITEMBOUNDSCHANGE, that._refreshHandler);
            len = shape.connectors.length;
            that.connectors = [];
            that.visual.clear();
            for (i = 0; i < len; i++) {
                ctr = new ConnectorVisual(shape.connectors[i]);
                that.connectors.push(ctr);
                that.visual.append(ctr.visual);
            }
            that.visual.visible(true);
            that.refresh();
        },

        destroy: function () {
            var that = this;
            that.diagram.unbind(ITEMBOUNDSCHANGE, that._refreshHandler);
            that.shape = undefined;
            that._visible = undefined;
            that.visual.visible(false);
        },

        _hitTest: function (p) {
            var ctr, i;
            for (i = 0; i < this.connectors.length; i++) {
                ctr = this.connectors[i];
                if (ctr._hitTest(p)) {
                    ctr._hover(true);
                    this.diagram.toolService._hoveredConnector = ctr;
                    break;
                }
            }
        },

        refresh: function () {
            if (this.shape) {
                var bounds = this.shape.bounds();
                bounds = this.diagram.modelToLayer(bounds);
                this.visual.position(bounds.topLeft());
                $.each(this.connectors, function () {
                    this.refresh();
                });
            }
        }
    });

    function hitToOppositeSide(hit, bounds) {
        var result;

        if (hit.x == -1 && hit.y == -1) {
            result = bounds.bottomRight();
        } else if (hit.x == 1 && hit.y == 1) {
            result = bounds.topLeft();
        } else if (hit.x == -1 && hit.y == 1) {
            result = bounds.topRight();
        } else if (hit.x == 1 && hit.y == -1) {
            result = bounds.bottomLeft();
        } else if (hit.x === 0 && hit.y == -1) {
            result = bounds.bottom();
        } else if (hit.x === 0 && hit.y == 1) {
            result = bounds.top();
        } else if (hit.x == 1 && hit.y === 0) {
            result = bounds.left();
        } else if (hit.x == -1 && hit.y === 0) {
            result = bounds.right();
        }

        return result;
    }

    var ResizingAdorner = AdornerBase.extend({
        init: function (diagram, options) {
            var that = this;
            AdornerBase.fn.init.call(that, diagram, options);
            that._manipulating = false;
            that.map = [];
            that.shapes = [];

            that._initSelection();
            that._createHandles();
            that.redraw();
            that.diagram.bind("select", function (e) {
                that._initialize(e.selected);
            });

            that._refreshHandler = function () {
                if (!that._internalChange) {
                    that.refreshBounds();
                    that.refresh();
                }
            };

            that._rotatedHandler = function () {
                if (that.shapes.length == 1) {
                    that._angle = that.shapes[0].rotate().angle;
                }
                that._refreshHandler();
            };

            that.diagram.bind(ITEMBOUNDSCHANGE, that._refreshHandler).bind(ITEMROTATE, that._rotatedHandler);
            that.refreshBounds();
            that.refresh();
        },

        options: {
            editable: {
            },
            selectable: {
                stroke: {
                    color: "#778899",
                    width: 1,
                    dashType: "dash"
                },
                fill: {
                    color: TRANSPARENT
                }
            },
            offset: 10
        },

        _initSelection: function () {
            var that = this;
            var diagram = that.diagram;
            var selectable = diagram.options.selectable;
            var options = deepExtend({}, that.options.selectable, selectable);
            that.rect = new Rectangle(options);
            that.visual.append(that.rect);
        },

        _createHandles: function () {
            var editable = this.options.editable,
                    handles, item, i, y, x;

            if (editable && editable.resize) {
                handles = editable.resize.handles;
                for (x = -1; x <= 1; x++) {
                    for (y = -1; y <= 1; y++) {
                        if ((x !== 0) || (y !== 0)) { // (0, 0) element, (-1, -1) top-left, (+1, +1) bottom-right
                            item = new Rectangle(handles);
                            item.drawingElement._hover = proxy(this._hover, this);
                            this.map.push({ x: x, y: y, visual: item });
                            this.visual.append(item);
                        }
                    }
                }
            }
        },

        bounds: function (value) {
            if (value) {
                this._innerBounds = value.clone();
                this._bounds = this.diagram.modelToLayer(value).inflate(this.options.offset, this.options.offset);
            } else {
                return this._bounds;
            }
        },

        _hitTest: function (p) {
            var tp = this.diagram.modelToLayer(p),
                    editable = this.options.editable,
                    i, hit, handleBounds, handlesCount = this.map.length, handle;

            if (this._angle) {
                tp = tp.clone().rotate(this._bounds.center(), this._angle);
            }

            if (editable && editable.rotate && this._rotationThumbBounds) {
                if (this._rotationThumbBounds.contains(tp)) {
                    return new Point(-1, -2);
                }
            }

            if (editable && editable.resize) {
                for (i = 0; i < handlesCount; i++) {
                    handle = this.map[i];
                    hit = new Point(handle.x, handle.y);
                    handleBounds = this._getHandleBounds(hit); //local coordinates
                    handleBounds.offset(this._bounds.x, this._bounds.y);
                    if (handleBounds.contains(tp)) {
                        return hit;
                    }
                }
            }

            if (this._bounds.contains(tp)) {
                return new Point(0, 0);
            }
        },

        _getHandleBounds: function (p) {
            var editable = this.options.editable;
            if (editable && editable.resize) {
                var handles = editable.resize.handles || {},
                        w = handles.width,
                        h = handles.height,
                        r = new Rect(0, 0, w, h);

                if (p.x < 0) {
                    r.x = -w / 2;
                } else if (p.x === 0) {
                    r.x = Math.floor(this._bounds.width / 2) - w / 2;
                } else if (p.x > 0) {
                    r.x = this._bounds.width + 1.0 - w / 2;
                } if (p.y < 0) {
                    r.y = -h / 2;
                } else if (p.y === 0) {
                    r.y = Math.floor(this._bounds.height / 2) - h / 2;
                } else if (p.y > 0) {
                    r.y = this._bounds.height + 1.0 - h / 2;
                }

                return r;
            }
        },

        _getCursor: function (point) {
            var hit = this._hitTest(point);
            if (hit && (hit.x >= -1) && (hit.x <= 1) && (hit.y >= -1) && (hit.y <= 1) && this.options.editable && this.options.editable.resize) {
                var angle = this._angle;
                if (angle) {
                    angle = 360 - angle;
                    hit.rotate(new Point(0, 0), angle);
                    hit = new Point(Math.round(hit.x), Math.round(hit.y));
                }

                if (hit.x == -1 && hit.y == -1) {
                    return "nw-resize";
                }
                if (hit.x == 1 && hit.y == 1) {
                    return "se-resize";
                }
                if (hit.x == -1 && hit.y == 1) {
                    return "sw-resize";
                }
                if (hit.x == 1 && hit.y == -1) {
                    return "ne-resize";
                }
                if (hit.x === 0 && hit.y == -1) {
                    return "n-resize";
                }
                if (hit.x === 0 && hit.y == 1) {
                    return "s-resize";
                }
                if (hit.x == 1 && hit.y === 0) {
                    return "e-resize";
                }
                if (hit.x == -1 && hit.y === 0) {
                    return "w-resize";
                }
            }
            return this._manipulating ? Cursors.move : Cursors.select;
        },

        _initialize: function () {
            var that = this, i, item,
                    items = that.diagram.select();

            that.shapes = [];
            for (i = 0; i < items.length; i++) {
                item = items[i];
                if (item instanceof diagram.Shape) {
                    that.shapes.push(item);
                    item._rotationOffset = new Point();
                }
            }

            that._angle = that.shapes.length == 1 ? that.shapes[0].rotate().angle : 0;
            that._startAngle = that._angle;
            that._rotates();
            that._positions();
            that.refreshBounds();
            that.refresh();
            that.redraw();
        },

        _rotates: function () {
            var that = this, i, shape;
            that.initialRotates = [];
            for (i = 0; i < that.shapes.length; i++) {
                shape = that.shapes[i];
                that.initialRotates.push(shape.rotate().angle);
            }
        },

        _positions: function () {
            var that = this, i, shape;
            that.initialStates = [];
            for (i = 0; i < that.shapes.length; i++) {
                shape = that.shapes[i];
                that.initialStates.push(shape.bounds());
            }
        },

        _hover: function (value, element) {
            var editable = this.options.editable;
            if (editable && editable.resize) {
                var handleOptions = editable.resize.handles,
                        hover = handleOptions.hover,
                        stroke = handleOptions.stroke,
                        fill = handleOptions.fill;

                if (value && Utils.isDefined(hover.stroke)) {
                    stroke = deepExtend({}, stroke, hover.stroke);
                }

                if (value && Utils.isDefined(hover.fill)) {
                    fill = hover.fill;
                }
                element.stroke(stroke.color, stroke.width, stroke.opacity);
                element.fill(fill.color, fill.opacity);
            }
        },

        start: function (p) {
            this._sp = p;
            this._cp = p;
            this._lp = p;
            this._manipulating = true;
            this._internalChange = true;
            this.shapeStates = [];
            for (var i = 0; i < this.shapes.length; i++) {
                var shape = this.shapes[i];
                this.shapeStates.push(shape.bounds());
            }
        },

        redraw: function () {
            var that = this, i, handle,
                    editable = that.options.editable,
                    resize = editable.resize,
                    rotate = editable.rotate,
                    visibleHandles = editable && resize ? true : false,
                    visibleThumb = editable && rotate ? true : false;

            for (i = 0; i < this.map.length; i++) {
                handle = this.map[i];
                handle.visual.visible(visibleHandles);
            }

            if (that.rotationThumb) {
                that.rotationThumb.visible(visibleThumb);
            }
        },

        angle: function (value) {
            if (defined(value)) {
                this._angle = value;
            }

            return this._angle;
        },

        rotate: function () {
            var center = this._innerBounds.center();
            var currentAngle = this.angle();
            this._internalChange = true;
            for (var i = 0; i < this.shapes.length; i++) {
                var shape = this.shapes[i];
                currentAngle = (currentAngle + this.initialRotates[i] - this._startAngle) % 360;
                shape.rotate(currentAngle, center);
            }
            this.refresh();
        },

        move: function (handle, p) {
            var delta, dragging,
                    dtl = new Point(),
                    dbr = new Point(),
                    bounds, center, shape,
                    i, angle, newBounds,
                    changed = 0, staticPoint,
                    scaleX, scaleY;

            if (handle.y === -2 && handle.x === -1) {
                center = this._innerBounds.center();
                this._angle = this._truncateAngle(Utils.findAngle(center, p));
                for (i = 0; i < this.shapes.length; i++) {
                    shape = this.shapes[i];
                    angle = (this._angle + this.initialRotates[i] - this._startAngle) % 360;
                    shape.rotate(angle, center);
                    if (shape.hasOwnProperty("layout")) {
                        shape.layout(shape);
                    }
                    this._rotating = true;
                }
                this.refresh();
            } else {
                if (this.diagram.options.snap.enabled === true) {
                    var thr = this._truncateDistance(p.minus(this._lp));
                    // threshold
                    if (thr.x === 0 && thr.y === 0) {
                        this._cp = p;
                        return;
                    }
                    delta = thr;
                    this._lp = new Point(this._lp.x + thr.x, this._lp.y + thr.y);
                } else {
                    delta = p.minus(this._cp);
                }

                if (handle.x === 0 && handle.y === 0) {
                    dbr = dtl = delta; // dragging
                    dragging = true;
                } else {
                    if (this._angle) { // adjust the delta so that resizers resize in the correct direction after rotation.
                        delta.rotate(new Point(0, 0), this._angle);
                    }
                    if (handle.x == -1) {
                        dtl.x = delta.x;
                    } else if (handle.x == 1) {
                        dbr.x = delta.x;
                    }
                    if (handle.y == -1) {
                        dtl.y = delta.y;
                    } else if (handle.y == 1) {
                        dbr.y = delta.y;
                    }
                }

                if (!dragging) {
                    staticPoint = hitToOppositeSide(handle, this._innerBounds);
                    scaleX = (this._innerBounds.width + delta.x * handle.x) / this._innerBounds.width;
                    scaleY = (this._innerBounds.height + delta.y * handle.y) / this._innerBounds.height;
                }

                for (i = 0; i < this.shapes.length; i++) {
                    shape = this.shapes[i];
                    bounds = shape.bounds();
                    if (dragging) {
                        newBounds = this._displaceBounds(bounds, dtl, dbr, dragging);
                    } else {
                        newBounds = bounds.clone();
                        newBounds.scale(scaleX, scaleY, staticPoint, this._innerBounds.center(), shape.rotate().angle);
                        var newCenter = newBounds.center(); // fixes the new rotation center.
                        newCenter.rotate(bounds.center(), -this._angle);
                        newBounds = new Rect(newCenter.x - newBounds.width / 2, newCenter.y - newBounds.height / 2, newBounds.width, newBounds.height);
                    }
                    if (newBounds.width >= shape.options.minWidth && newBounds.height >= shape.options.minHeight) { // if we up-size very small shape
                        var oldBounds = bounds;
                        shape.bounds(newBounds);
                        if (shape.hasOwnProperty("layout")) {
                            shape.layout(shape, oldBounds, newBounds);
                        }
                        shape.rotate(shape.rotate().angle); // forces the rotation to update it's rotation center
                        changed += 1;
                    }
                }

                if (changed == i) {
                    newBounds = this._displaceBounds(this._innerBounds, dtl, dbr, dragging);
                    this.bounds(newBounds);
                    this.refresh();
                }

                this._positions();
            }

            this._cp = p;
        },

        _truncatePositionToGuides: function (bounds) {
            if (this.diagram.ruler) {
                return this.diagram.ruler.truncatePositionToGuides(bounds);
            }
            return bounds;
        },

        _truncateSizeToGuides: function (bounds) {
            if (this.diagram.ruler) {
                return this.diagram.ruler.truncateSizeToGuides(bounds);
            }
            return bounds;
        },

        _truncateAngle: function (a) {
            var snapAngle = Math.max(this.diagram.options.snap.angle, 5);
            return this.diagram.options.snap.enabled === true ? Math.floor((a % 360) / snapAngle) * snapAngle : (a % 360);
        },

        _truncateDistance: function (d) {
            if (d instanceof diagram.Point) {
                return new diagram.Point(this._truncateDistance(d.x), this._truncateDistance(d.y));
            } else {
                var snapSize = Math.max(this.diagram.options.snap.size, 5);
                return this.diagram.options.snap.enabled === true ? Math.floor(d / snapSize) * snapSize : d;
            }
        },

        _displaceBounds: function (bounds, dtl, dbr, dragging) {
            var tl = bounds.topLeft().plus(dtl),
                    br = bounds.bottomRight().plus(dbr),
                    newBounds = Rect.fromPoints(tl, br),
                    newCenter;
            if (!dragging) {
                newCenter = newBounds.center();
                newCenter.rotate(bounds.center(), -this._angle);
                newBounds = new Rect(newCenter.x - newBounds.width / 2, newCenter.y - newBounds.height / 2, newBounds.width, newBounds.height);
            }
            return newBounds;
        },

        stop: function () {
            var unit, i, shape;
            if (this._cp != this._sp) {
                if (this._rotating) {
                    unit = new RotateUnit(this, this.shapes, this.initialRotates);
                    this._rotating = false;
                } else {
                    if (this.diagram.ruler) {
                        for (i = 0; i < this.shapes.length; i++) {
                            shape = this.shapes[i];
                            var bounds = shape.bounds();
                            bounds = this._truncateSizeToGuides(this._truncatePositionToGuides(bounds));
                            shape.bounds(bounds);
                            this.refreshBounds();
                            this.refresh();
                        }
                    }
                    for (i = 0; i < this.shapes.length; i++) {
                        shape = this.shapes[i];
                        shape.updateModel();
                    }
                    unit = new TransformUnit(this.shapes, this.shapeStates, this);
                }
            }

            this._manipulating = undefined;
            this._internalChange = undefined;
            this._rotating = undefined;
            return unit;
        },

        refreshBounds: function () {
            var bounds = this.shapes.length == 1 ?
                    this.shapes[0].bounds().clone() :
                    this.diagram.boundingBox(this.shapes, true);

            this.bounds(bounds);
        },

        refresh: function () {
            var that = this, b, bounds;
            if (this.shapes.length > 0) {
                bounds = this.bounds();
                this.visual.visible(true);
                this.visual.position(bounds.topLeft());
                $.each(this.map, function () {
                    b = that._getHandleBounds(new Point(this.x, this.y));
                    this.visual.position(b.topLeft());
                });
                this.visual.position(bounds.topLeft());

                var center = new Point(bounds.width / 2, bounds.height / 2);
                this.visual.rotate(this._angle, center);
                this.rect.redraw({ width: bounds.width, height: bounds.height });
                if (this.rotationThumb) {
                    var thumb = this.options.editable.rotate.thumb;
                    this._rotationThumbBounds = new Rect(bounds.center().x, bounds.y + thumb.y, 0, 0).inflate(thumb.width);
                    this.rotationThumb.redraw({ x: bounds.width / 2 - thumb.width / 2 });
                }
            } else {
                this.visual.visible(false);
            }
        }
    });

    var Selector = Class.extend({
        init: function (diagram) {
            var selectable = diagram.options.selectable;
            this.options = deepExtend({}, this.options, selectable);

            this.visual = new Rectangle(this.options);
            this.diagram = diagram;
        },
        options: {
            stroke: {
                color: "#778899",
                width: 1,
                dashType: "dash"
            },
            fill: {
                color: TRANSPARENT
            }
        },
        start: function (p) {
            this._sp = this._ep = p;
            this.refresh();
            this.diagram._adorn(this, true);
        },
        end: function () {
            this._sp = this._ep = undefined;
            this.diagram._adorn(this, false);
        },
        bounds: function (value) {
            if (value) {
                this._bounds = value;
            }
            return this._bounds;
        },
        move: function (p) {
            this._ep = p;
            this.refresh();
        },
        refresh: function () {
            if (this._sp) {
                var visualBounds = Rect.fromPoints(this.diagram.modelToLayer(this._sp), this.diagram.modelToLayer(this._ep));
                this.bounds(Rect.fromPoints(this._sp, this._ep));
                this.visual.position(visualBounds.topLeft());
                this.visual.redraw({ height: visualBounds.height + 1, width: visualBounds.width + 1 });
            }
        }
    });

    var ConnectorVisual = Class.extend({
        init: function (connector) {
            this.options = deepExtend({}, connector.options);
            this._c = connector;
            this.visual = new Circle(this.options);
            this.refresh();
        },
        _hover: function (value) {
            var options = this.options,
                    hover = options.hover,
                    stroke = options.stroke,
                    fill = options.fill;

            if (value && Utils.isDefined(hover.stroke)) {
                stroke = deepExtend({}, stroke, hover.stroke);
            }

            if (value && Utils.isDefined(hover.fill)) {
                fill = hover.fill;
            }

            this.visual.redraw({
                stroke: stroke,
                fill: fill
            });
        },
        refresh: function () {
            var p = this._c.shape.diagram.modelToView(this._c.position()),
                    relative = p.minus(this._c.shape.bounds("transformed").topLeft()),
                    value = new Rect(p.x, p.y, 0, 0);
            value.inflate(this.options.width / 2, this.options.height / 2);
            this._visualBounds = value;
            this.visual.redraw({ center: new Point(relative.x, relative.y) });
        },
        _hitTest: function (p) {
            var tp = this._c.shape.diagram.modelToView(p);
            return this._visualBounds.contains(tp);
        }
    });

    deepExtend(diagram, {
        CompositeUnit: CompositeUnit,
        TransformUnit: TransformUnit,
        PanUndoUnit: PanUndoUnit,
        AddShapeUnit: AddShapeUnit,
        AddConnectionUnit: AddConnectionUnit,
        DeleteShapeUnit: DeleteShapeUnit,
        DeleteConnectionUnit: DeleteConnectionUnit,
        ConnectionEditAdorner: ConnectionEditAdorner,
        UndoRedoService: UndoRedoService,
        ResizingAdorner: ResizingAdorner,
        Selector: Selector,
        ToolService: ToolService,
        ConnectorsAdorner: ConnectorsAdorner,
        LayoutUndoUnit: LayoutUndoUnit,
        ConnectionEditUnit: ConnectionEditUnit,
        ToFrontUnit: ToFrontUnit,
        ToBackUnit: ToBackUnit,
        ConnectionRouterBase: ConnectionRouterBase,
        PolylineRouter: PolylineRouter,
        CascadingRouter: CascadingRouter,
        SelectionTool: SelectionTool,
        ScrollerTool: ScrollerTool,
        PointerTool: PointerTool,
        ConnectionEditTool: ConnectionEditTool,
        RotateUnit: RotateUnit
    });
})(window.kendo.jQuery);

(function ($, undefined) {
    var kendo = window.kendo,
        diagram = kendo.dataviz.diagram,
        Graph = diagram.Graph,
        Node = diagram.Node,
        Link = diagram.Link,
        deepExtend = kendo.deepExtend,
        Size = diagram.Size,
        Rect = diagram.Rect,
        Dictionary = diagram.Dictionary,
        Set = diagram.Set,
        HyperTree = diagram.Graph,
        Utils = diagram.Utils,
        Point = diagram.Point,
        EPSILON = 1e-06,
        DEG_TO_RAD = Math.PI / 180,
        contains = Utils.contains,
        grep = $.grep;

    /**
    * Base class for layout algorithms.
    * @type {*}
    */
    var LayoutBase = kendo.Class.extend({
        defaultOptions: {
            type: "Tree",
            subtype: "Down",
            roots: null,
            animate: false,
            //-------------------------------------------------------------------
            /**
            * Force-directed option: whether the motion of the nodes should be limited by the boundaries of the diagram surface.
            */
            limitToView: false,
            /**
            * Force-directed option: the amount of friction applied to the motion of the nodes.
            */
            friction: 0.9,
            /**
            * Force-directed option: the optimal distance between nodes (minimum energy).
            */
            nodeDistance: 50,
            /**
            * Force-directed option: the number of time things are being calculated.
            */
            iterations: 300,
            //-------------------------------------------------------------------
            /**
            * Tree option: the separation in one direction (depends on the subtype what direction this is).
            */
            horizontalSeparation: 90,
            /**
            * Tree option: the separation in the complementary direction (depends on the subtype what direction this is).
            */
            verticalSeparation: 50,

            //-------------------------------------------------------------------
            /**
            * Tip-over tree option: children-to-parent vertical distance.
            */
            underneathVerticalTopOffset: 15,
            /**
            * Tip-over tree option: children-to-parent horizontal distance.
            */
            underneathHorizontalOffset: 15,
            /**
            * Tip-over tree option: leaf-to-next-branch vertical distance.
            */
            underneathVerticalSeparation: 15,
            //-------------------------------------------------------------------
            /**
            * Settings object to organize the different components of the diagram in a grid layout structure
            */
            grid: {
                /**
                * The width of the grid in which components are arranged. Beyond this width a component will be on the next row.
                */
                width: 1500,
                /**
                * The left offset of the grid.
                */
                offsetX: 50,
                /**
                * The top offset of the grid.
                */
                offsetY: 50,
                /**
                * The horizontal padding within a cell of the grid where a single component resides.
                */
                componentSpacingX: 20,
                /**
                * The vertical padding within a cell of the grid where a single component resides.
                */
                componentSpacingY: 20
            },

            //-------------------------------------------------------------------
            /**
            * Layered option: the separation height/width between the layers.
            */
            layerSeparation: 50,
            /**
            * Layered option: how many rounds of shifting and fine-tuning.
            */
            layeredIterations: 2,
            /**
            * Tree-radial option: the angle at which the layout starts.
            */
            startRadialAngle: 0,
            /**
            * Tree-radial option: the angle at which the layout starts.
            */
            endRadialAngle: 360,
            /**
            * Tree-radial option: the separation between levels.
            */
            radialSeparation: 150,
            /**
            * Tree-radial option: the separation between the root and the first level.
            */
            radialFirstLevelSeparation: 200,
            /**
            * Tree-radial option: whether a virtual roots bing the components in one radial layout.
            */
            keepComponentsInOneRadialLayout: false,
            //-------------------------------------------------------------------

            // TODO: ensure to change this to false when containers are around
            ignoreContainers: true,
            layoutContainerChildren: false,
            ignoreInvisible: true,
            animateTransitions: false
        },
        init: function () {
        },

        /**
        * Organizes the components in a grid.
        * Returns the final set of nodes (not the Graph).
        * @param components
        */
        gridLayoutComponents: function (components) {
            if (!components) {
                throw "No components supplied.";
            }

            // calculate and cache the bounds of the components
            Utils.forEach(components, function (c) {
                c.calcBounds();
            });

            // order by decreasing width
            components.sort(function (a, b) {
                return b.bounds.width - a.bounds.width;
            });

            var maxWidth = this.options.grid.width,
                offsetX = this.options.grid.componentSpacingX,
                offsetY = this.options.grid.componentSpacingY,
                height = 0,
                startX = this.options.grid.offsetX,
                startY = this.options.grid.offsetY,
                x = startX,
                y = startY,
                i,
                resultLinkSet = [],
                resultNodeSet = [];

            while (components.length > 0) {
                if (x >= maxWidth) {
                    // start a new row
                    x = startX;
                    y += height + offsetY;
                    // reset the row height
                    height = 0;
                }
                var component = components.pop();
                this.moveToOffset(component, new Point(x, y));
                for (i = 0; i < component.nodes.length; i++) {
                    resultNodeSet.push(component.nodes[i]); // to be returned in the end
                }
                for (i = 0; i < component.links.length; i++) {
                    resultLinkSet.push(component.links[i]);
                }
                var boundingRect = component.bounds;
                var currentHeight = boundingRect.height;
                if (currentHeight <= 0 || isNaN(currentHeight)) {
                    currentHeight = 0;
                }
                var currentWidth = boundingRect.width;
                if (currentWidth <= 0 || isNaN(currentWidth)) {
                    currentWidth = 0;
                }

                if (currentHeight >= height) {
                    height = currentHeight;
                }
                x += currentWidth + offsetX;
            }

            return {
                nodes: resultNodeSet,
                links: resultLinkSet
            };
        },

        moveToOffset: function (component, p) {
            var i, j,
                bounds = component.bounds,
                deltax = p.x - bounds.x,
                deltay = p.y - bounds.y;

            for (i = 0; i < component.nodes.length; i++) {
                var node = component.nodes[i];
                var nodeBounds = node.bounds();
                if (nodeBounds.width === 0 && nodeBounds.height === 0 && nodeBounds.x === 0 && nodeBounds.y === 0) {
                    nodeBounds = new Rect(0, 0, 0, 0);
                }
                nodeBounds.x += deltax;
                nodeBounds.y += deltay;
                node.bounds(nodeBounds);
            }
            for (i = 0; i < component.links.length; i++) {
                var link = component.links[i];
                if (link.points) {
                    var newpoints = [];
                    var points = link.points;
                    for (j = 0; j < points.length; j++) {
                        var pt = points[j];
                        pt.x += deltax;
                        pt.y += deltay;
                        newpoints.push(pt);
                    }
                    link.points = newpoints;
                }
            }
            this.currentHorizontalOffset += bounds.width + this.options.grid.offsetX;
            return new Point(deltax, deltay);
        },

        transferOptions: function (options) {

            // Size options lead to stackoverflow and need special handling

            this.options = kendo.deepExtend({}, this.defaultOptions);
            if (Utils.isUndefined(options)) {
                return;
            }

            this.options = kendo.deepExtend(this.options, options || {});
        }
    });

    /**
    * The data bucket a hypertree holds in its nodes.     *
    * @type {*}
    */
    /* var ContainerGraph = kendo.Class.extend({
    init: function (diagram) {
    this.diagram = diagram;
    this.graph = new Graph(diagram);
    this.container = null;
    this.containerNode = null;
    }

    });*/

    /**
    * Adapter between the diagram control and the graph representation. It converts shape and connections to nodes and edges taking into the containers and their collapsef state,
    * the visibility of items and more. If the layoutContainerChildren is true a hypertree is constructed which holds the hierarchy of containers and many conditions are analyzed
    * to investigate how the effective graph structure looks like and how the layout has to be performed.
    * @type {*}
    */
    var DiagramToHyperTreeAdapter = kendo.Class.extend({
        init: function (diagram) {

            /**
            * The mapping to/from the original nodes.
            * @type {Dictionary}
            */
            this.nodeMap = new Dictionary();

            /**
            * Gets the mapping of a shape to a container in case the shape sits in a collapsed container.
            * @type {Dictionary}
            */
            this.shapeMap = new Dictionary();

            /**
            * The nodes being mapped.
            * @type {Dictionary}
            */
            this.nodes = [];

            /**
            * The connections being mapped.
            * @type {Dictionary}
            */
            this.edges = [];

            // the mapping from an edge to all the connections it represents, this can be both because of multiple connections between
            // two shapes or because a container holds multiple connections to another shape or container.
            this.edgeMap = new Dictionary();

            /**
            * The resulting set of Nodes when the analysis has finished.
            * @type {Array}
            */
            this.finalNodes = [];

            /**
            * The resulting set of Links when the analysis has finished.
            * @type {Array}
            */
            this.finalLinks = [];

            /**
            * The items being omitted because of multigraph edges.
            * @type {Array}
            */
            this.ignoredConnections = [];

            /**
            * The items being omitted because of containers, visibility and other factors.
            * @type {Array}
            */
            this.ignoredShapes = [];

            /**
            * The map from a node to the partition/hypernode in which it sits. This hyperMap is null if 'options.layoutContainerChildren' is false.
            * @type {Dictionary}
            */
            this.hyperMap = new Dictionary();

            /**
            * The hypertree contains the hierarchy defined by the containers.
            * It's in essence a Graph of Graphs with a tree structure defined by the hierarchy of containers.
            * @type {HyperTree}
            */
            this.hyperTree = new Graph();

            /**
            * The resulting graph after conversion. Note that this does not supply the information contained in the
            * ignored connection and shape collections.
            * @type {null}
            */
            this.finalGraph = null;

            this.diagram = diagram;
        },

        /**
        * The hyperTree is used when the 'options.layoutContainerChildren' is true. It contains the hierarchy of containers whereby each node is a ContainerGraph.
        * This type of node has a Container reference to the container which holds the Graph items. There are three possible situations during the conversion process:
        *  - Ignore the containers: the container are non-existent and only normal shapes are mapped. If a shape has a connection to a container it will be ignored as well
        *    since there is no node mapped for the container.
        *  - Do not ignore the containers and leave the content of the containers untouched: the top-level elements are being mapped and the children within a container are not altered.
        *  - Do not ignore the containers and organize the content of the containers as well: the hypertree is constructed and there is a partitioning of all nodes and connections into the hypertree.
        *    The only reason a connection or node is not being mapped might be due to the visibility, which includes the visibility change through a collapsed parent container.
        * @param options
        */
        convert: function (options) {

            if (Utils.isUndefined(this.diagram)) {
                throw "No diagram to convert.";
            }

            this.options = kendo.deepExtend({
                ignoreInvisible: true,
                ignoreContainers: true,
                layoutContainerChildren: false
            },
                options || {}
            );

            this.clear();
            // create the nodes which participate effectively in the graph analysis
            this._renormalizeShapes();

            // recreate the incoming and outgoing collections of each and every node
            this._renormalizeConnections();

            // export the resulting graph
            this.finalNodes = new Dictionary(this.nodes);
            this.finalLinks = new Dictionary(this.edges);

            this.finalGraph = new Graph();
            this.finalNodes.forEach(function (n) {
                this.finalGraph.addNode(n);
            }, this);
            this.finalLinks.forEach(function (l) {
                this.finalGraph.addExistingLink(l);
            }, this);
            return this.finalGraph;
        },

        /**
        * Maps the specified connection to an edge of the graph deduced from the given diagram.
        * @param connection
        * @returns {*}
        */
        mapConnection: function (connection) {
            return this.edgeMap.first(function (edge) {
                return contains(this.edgeMap.get(edge), connection);
            });
        },

        /**
        * Maps the specified shape to a node of the graph deduced from the given diagram.
        * @param shape
        * @returns {*}
        */
        mapShape: function (shape) {
            var keys = this.nodeMap.keys();
            for (var i = 0, len = keys.length; i < len; i++) {
                var key = keys[i];
                if (contains(this.nodeMap.get(key), shape)) {
                    return key;
                }
            }
        },

        /**
        * Gets the edge, if any, between the given nodes.
        * @param a
        * @param b
        */
        getEdge: function (a, b) {
            return Utils.first(a.links, function (link) {
                return link.getComplement(a) === b;
            });
        },

        /**
        * Clears all the collections used by the conversion process.
        */
        clear: function () {
            this.finalGraph = null;
            this.hyperTree = (!this.options.ignoreContainers && this.options.layoutContainerChildren) ? new HyperTree() : null;
            this.hyperMap = (!this.options.ignoreContainers && this.options.layoutContainerChildren) ? new Dictionary() : null;
            this.nodeMap = new Dictionary();
            this.shapeMap = new Dictionary();
            this.nodes = [];
            this.edges = [];
            this.edgeMap = new Dictionary();
            this.ignoredConnections = [];
            this.ignoredShapes = [];
            this.finalNodes = [];
            this.finalLinks = [];
        },

        /**
        * The path from a given ContainerGraph to the root (container).
        * @param containerGraph
        * @returns {Array}
        */
        listToRoot: function (containerGraph) {
            var list = [];
            var s = containerGraph.container;
            if (!s) {
                return list;
            }
            list.push(s);
            while (s.parentContainer) {
                s = s.parentContainer;
                list.push(s);
            }
            list.reverse();
            return list;
        },

        firstNonIgnorableContainer: function (shape) {

            if (shape.isContainer && !this._isIgnorableItem(shape)) {
                return shape;
            }
            return !shape.parentContainer ? null : this.firstNonIgnorableContainer(shape.parentContainer);
        },
        isContainerConnection: function (a, b) {
            if (a.isContainer && this.isDescendantOf(a, b)) {
                return true;
            }
            return b.isContainer && this.isDescendantOf(b, a);
        },

        /**
        * Returns true if the given shape is a direct child or a nested container child of the given container.
        * If the given container and shape are the same this will return false since a shape cannot be its own child.
        * @param scope
        * @param a
        * @returns {boolean}
        */
        isDescendantOf: function (scope, a) {
            if (!scope.isContainer) {
                throw "Expecting a container.";
            }
            if (scope === a) {
                return false;
            }
            if (contains(scope.children, a)) {
                return true;
            }
            var containers = [];
            for (var i = 0, len = scope.children.length; i < len; i++) {
                var c = scope.children[i];
                if (c.isContainer && this.isDescendantOf(c, a)) {
                    containers.push(c);
                }
            }

            return containers.length > 0;
        },
        isIgnorableItem: function (shape) {
            if (this.options.ignoreInvisible) {
                if (shape.isCollapsed && this._isVisible(shape)) {
                    return false;
                }
                if (!shape.isCollapsed && this._isVisible(shape)) {
                    return false;
                }
                return true;
            }
            else {
                return shape.isCollapsed && !this._isTop(shape);
            }
        },

        /**
        *  Determines whether the shape is or needs to be mapped to another shape. This occurs essentially when the shape sits in
        * a collapsed container hierarchy and an external connection needs a node endpoint. This node then corresponds to the mapped shape and is
        * necessarily a container in the parent hierarchy of the shape.
        * @param shape
        */
        isShapeMapped: function (shape) {
            return shape.isCollapsed && !this._isVisible(shape) && !this._isTop(shape);
        },

        leastCommonAncestor: function (a, b) {
            if (!a) {
                throw "Parameter should not be null.";
            }
            if (!b) {
                throw "Parameter should not be null.";
            }

            if (!this.hyperTree) {
                throw "No hypertree available.";
            }
            var al = this.listToRoot(a);
            var bl = this.listToRoot(b);
            var found = null;
            if (Utils.isEmpty(al) || Utils.isEmpty(bl)) {
                return this.hyperTree.root.data;
            }
            var xa = al[0];
            var xb = bl[0];
            var i = 0;
            while (xa === xb) {
                found = al[i];
                i++;
                if (i >= al.length || i >= bl.length) {
                    break;
                }
                xa = al[i];
                xb = bl[i];
            }
            if (!found) {
                return this.hyperTree.root.data;
            }
            else {
                return grep(this.hyperTree.nodes, function (n) {
                    return n.data.container === found;
                });
            }
        },
        /**
        * Determines whether the specified item is a top-level shape or container.
        * @param item
        * @returns {boolean}
        * @private
        */
        _isTop: function (item) {
            return !item.parentContainer;
        },

        /**
        * Determines iteratively (by walking up the container stack) whether the specified shape is visible.
        * This does NOT tell whether the item is not visible due to an explicit Visibility change or due to a collapse state.
        * @param shape
        * @returns {*}
        * @private
        */
        _isVisible: function (shape) {

            if (!shape.visible()) {
                return false;
            }
            return !shape.parentContainer ? shape.visible() : this._isVisible(shape.parentContainer);
        },

        _isCollapsed: function (shape) {

            if (shape.isContainer && shape.isCollapsed) {
                return true;
            }
            return shape.parentContainer && this._isCollapsed(shape.parentContainer);
        },

        /**
        * First part of the graph creation; analyzing the shapes and containers and deciding whether they should be mapped to a Node.
        * @private
        */
        _renormalizeShapes: function () {
            // add the nodes, the adjacency structure will be reconstructed later on
            if (this.options.ignoreContainers) {
                for (var i = 0, len = this.diagram.shapes.length; i < len; i++) {
                    var shape = this.diagram.shapes[i];

                    // if not visible (and ignoring the invisible ones) or a container we skip
                    if ((this.options.ignoreInvisible && !this._isVisible(shape)) || shape.isContainer) {
                        this.ignoredShapes.push(shape);
                        continue;
                    }
                    var node = new Node(shape.id, shape);
                    node.isVirtual = false;

                    // the mapping will always contain singletons and the hyperTree will be null
                    this.nodeMap.add(node, [shape]);
                    this.nodes.push(node);
                }
            }
            else {
                throw "Containers are not supported yet, but stay tuned.";
            }
        },

        /**
        * Second part of the graph creation; analyzing the connections and deciding whether they should be mapped to an edge.
        * @private
        */
        _renormalizeConnections: function () {
            if (this.diagram.connections.length === 0) {
                return;
            }
            for (var i = 0, len = this.diagram.connections.length; i < len; i++) {
                var conn = this.diagram.connections[i];

                if (this.isIgnorableItem(conn)) {
                    this.ignoredConnections.push(conn);
                    continue;
                }

                var source = !conn.sourceConnector ? null : conn.sourceConnector.shape;
                var sink = !conn.targetConnector ? null : conn.targetConnector.shape;

                // no layout for floating connections
                if (!source || !sink) {
                    this.ignoredConnections.push(conn);
                    continue;
                }

                if (contains(this.ignoredShapes, source) && !this.shapeMap.containsKey(source)) {
                    this.ignoredConnections.push(conn);
                    continue;
                }
                if (contains(this.ignoredShapes, sink) && !this.shapeMap.containsKey(sink)) {
                    this.ignoredConnections.push(conn);
                    continue;
                }

                // if the endpoint sits in a collapsed container we need the container rather than the shape itself
                if (this.shapeMap.containsKey(source)) {
                    source = this.shapeMap[source];
                }
                if (this.shapeMap.containsKey(sink)) {
                    sink = this.shapeMap[sink];
                }

                var sourceNode = this.mapShape(source);
                var sinkNode = this.mapShape(sink);
                if ((sourceNode === sinkNode) || this.areConnectedAlready(sourceNode, sinkNode)) {
                    this.ignoredConnections.push(conn);
                    continue;
                }

                if (sourceNode === null || sinkNode === null) {
                    throw "A shape was not mapped to a node.";
                }
                if (this.options.ignoreContainers) {
                    // much like a floating connection here since at least one end is attached to a container
                    if (sourceNode.isVirtual || sinkNode.isVirtual) {
                        this.ignoredConnections.push(conn);
                        continue;
                    }
                    var newEdge = new Link(sourceNode, sinkNode, conn.id, conn);

                    this.edgeMap.add(newEdge, [conn]);
                    this.edges.push(newEdge);
                }
                else {
                    throw "Containers are not supported yet, but stay tuned.";
                }
            }
        },

        areConnectedAlready: function (n, m) {
            return Utils.any(this.edges, function (l) {
                return l.source === n && l.target === m || l.source === m && l.target === n;
            });
        }

        /**
        * Depth-first traversal of the given container.
        * @param container
        * @param action
        * @param includeStart
        * @private
        */
        /* _visitContainer: function (container, action, includeStart) {

        */
        /*if (container == null) throw new ArgumentNullException("container");
        if (action == null) throw new ArgumentNullException("action");
        if (includeStart) action(container);
        if (container.children.isEmpty()) return;
        foreach(
        var item
        in
        container.children.OfType < IShape > ()
        )
        {
        var childContainer = item
        as
        IContainerShape;
        if (childContainer != null) this.VisitContainer(childContainer, action);
        else action(item);
        }*/
        /*
        }*/


    });

    /**
    * The classic spring-embedder (aka force-directed, Fruchterman-Rheingold, barycentric) algorithm.
    * http://en.wikipedia.org/wiki/Force-directed_graph_drawing
    *  - Chapter 12 of Tamassia et al. "Handbook of graph drawing and visualization".
    *  - Kobourov on preprint arXiv; http://arxiv.org/pdf/1201.3011.pdf
    *  - Fruchterman and Rheingold in SOFTWARE-PRACTICE AND EXPERIENCE, VOL. 21(1 1), 1129-1164 (NOVEMBER 1991)
    * @type {*}
    */
    var SpringLayout = LayoutBase.extend({
        init: function (diagram) {
            var that = this;
            LayoutBase.fn.init.call(that);
            if (Utils.isUndefined(diagram)) {
                throw "Diagram is not specified.";
            }
            this.diagram = diagram;
        },

        layout: function (options) {

            this.transferOptions(options);

            var adapter = new DiagramToHyperTreeAdapter(this.diagram);
            var graph = adapter.convert(options);
            if (graph.isEmpty()) {
                return;
            }
            // split into connected components
            var components = graph.getConnectedComponents();
            if (Utils.isEmpty(components)) {
                return;
            }
            for (var i = 0; i < components.length; i++) {
                var component = components[i];
                this.layoutGraph(component, options);
            }
            var finalNodeSet = this.gridLayoutComponents(components);
            return new diagram.LayoutState(this.diagram, finalNodeSet);
        },

        layoutGraph: function (graph, options) {

            if (Utils.isDefined(options)) {
                this.transferOptions(options);
            }
            this.graph = graph;

            var initialTemperature = this.options.nodeDistance * 9;
            this.temperature = initialTemperature;

            var guessBounds = this._expectedBounds();
            this.width = guessBounds.width;
            this.height = guessBounds.height;

            for (var step = 0; step < this.options.iterations; step++) {
                this.refineStage = step >= this.options.iterations * 5 / 6;
                this.tick();
                // exponential cooldown
                this.temperature = this.refineStage ?
                    initialTemperature / 30 :
                    initialTemperature * (1 - step / (2 * this.options.iterations));
            }
        },

        /**
        * Single iteration of the simulation.
        */
        tick: function () {
            var i;
            // collect the repulsive forces on each node
            for (i = 0; i < this.graph.nodes.length; i++) {
                this._repulsion(this.graph.nodes[i]);
            }

            // collect the attractive forces on each node
            for (i = 0; i < this.graph.links.length; i++) {
                this._attraction(this.graph.links[i]);
            }
            // update the positions
            for (i = 0; i < this.graph.nodes.length; i++) {
                var node = this.graph.nodes[i];
                var offset = Math.sqrt(node.dx * node.dx + node.dy * node.dy);
                if (offset === 0) {
                    return;
                }
                node.x += Math.min(offset, this.temperature) * node.dx / offset;
                node.y += Math.min(offset, this.temperature) * node.dy / offset;
                if (this.options.limitToView) {
                    node.x = Math.min(this.width, Math.max(node.width / 2, node.x));
                    node.y = Math.min(this.height, Math.max(node.height / 2, node.y));
                }
            }
        },

        /**
        * Shakes the node away from its current position to escape the deadlock.
        * @param node A Node.
        * @private
        */
        _shake: function (node) {
            // just a simple polar neighborhood
            var rho = Math.random() * this.options.nodeDistance / 4;
            var alpha = Math.random() * 2 * Math.PI;
            node.x += rho * Math.cos(alpha);
            node.y -= rho * Math.sin(alpha);
        },

        /**
        * The typical Coulomb-Newton force law F=k/r^2
        * @remark This only works in dimensions less than three.
        * @param d
        * @param n A Node.
        * @param m Another Node.
        * @returns {number}
        * @private
        */
        _InverseSquareForce: function (d, n, m) {
            var force;
            if (!this.refineStage) {
                force = Math.pow(d, 2) / Math.pow(this.options.nodeDistance, 2);
            }
            else {
                var deltax = n.x - m.x;
                var deltay = n.y - m.y;

                var wn = n.width / 2;
                var hn = n.height / 2;
                var wm = m.width / 2;
                var hm = m.height / 2;

                force = (Math.pow(deltax, 2) / Math.pow(wn + wm + this.options.nodeDistance, 2)) + (Math.pow(deltay, 2) / Math.pow(hn + hm + this.options.nodeDistance, 2));
            }
            return force * 4 / 3;
        },

        /**
        * The typical Hooke force law F=kr^2
        * @param d
        * @param n
        * @param m
        * @returns {number}
        * @private
        */
        _SquareForce: function (d, n, m) {
            return 1 / this._InverseSquareForce(d, n, m);
        },

        _repulsion: function (n) {
            n.dx = 0;
            n.dy = 0;
            Utils.forEach(this.graph.nodes, function (m) {
                if (m === n) {
                    return;
                }
                while (n.x === m.x && n.y === m.y) {
                    this._shake(m);
                }
                var vx = n.x - m.x;
                var vy = n.y - m.y;
                var distance = Math.sqrt(vx * vx + vy * vy);
                var r = this._SquareForce(distance, n, m) * 2;
                n.dx += (vx / distance) * r;
                n.dy += (vy / distance) * r;
            }, this);
        },
        _attraction: function (link) {
            var t = link.target;
            var s = link.source;
            if (s === t) {
                // loops induce endless shakes
                return;
            }
            while (s.x === t.x && s.y === t.y) {
                this._shake(t);
            }

            var vx = s.x - t.x;
            var vy = s.y - t.y;
            var distance = Math.sqrt(vx * vx + vy * vy);

            var a = this._InverseSquareForce(distance, s, t) * 5;
            var dx = (vx / distance) * a;
            var dy = (vy / distance) * a;
            t.dx += dx;
            t.dy += dy;
            s.dx -= dx;
            s.dy -= dy;
        },

        /**
        * Calculates the expected bounds after layout.
        * @returns {*}
        * @private
        */
        _expectedBounds: function () {

            var size, N = this.graph.nodes.length, /*golden ration optimal?*/ratio = 1.5, multiplier = 4;
            if (N === 0) {
                return size;
            }
            size = Utils.fold(this.graph.nodes, function (s, node) {
                var area = node.width * node.height;
                if (area > 0) {
                    s += Math.sqrt(area);
                    return s;
                }
                return 0;
            }, 0, this);
            var av = size / N;
            var squareSize = av * Math.ceil(Math.sqrt(N));
            var width = squareSize * Math.sqrt(ratio);
            var height = squareSize / Math.sqrt(ratio);
            return { width: width * multiplier, height: height * multiplier };
        }

    });

    var TreeLayoutProcessor = kendo.Class.extend({

        init: function (options) {
            this.center = null;
            this.options = options;
        },
        layout: function (treeGraph, root) {
            this.graph = treeGraph;
            if (!this.graph.nodes || this.graph.nodes.length === 0) {
                return;
            }

            if (!contains(this.graph.nodes, root)) {
                throw "The given root is not in the graph.";
            }

            this.center = root;
            this.graph.cacheRelationships();
            /* var nonull = this.graph.nodes.where(function (n) {
            return n.associatedShape != null;
            });*/

            // transfer the rects
            /*nonull.forEach(function (n) {
            n.Location = n.associatedShape.Position;
            n.NodeSize = n.associatedShape.ActualBounds.ToSize();
            }

            );*/

            // caching the children
            /* nonull.forEach(function (n) {
            n.children = n.getChildren();
            });*/

            this.layoutSwitch();

            // apply the layout to the actual visuals
            // nonull.ForEach(n => n.associatedShape.Position = n.Location);
        },

        layoutLeft: function (left) {
            this.setChildrenDirection(this.center, "Left", false);
            this.setChildrenLayout(this.center, "Default", false);
            var h = 0, w = 0, y, i, node;
            for (i = 0; i < left.length; i++) {
                node = left[i];
                node.TreeDirection = "Left";
                var s = this.measure(node, Size.Empty);
                w = Math.max(w, s.Width);
                h += s.height + this.options.verticalSeparation;
            }

            h -= this.options.verticalSeparation;
            var x = this.center.x - this.options.horizontalSeparation;
            y = this.center.y + ((this.center.height - h) / 2);
            for (i = 0; i < left.length; i++) {
                node = left[i];
                var p = new Point(x - node.Size.width, y);

                this.arrange(node, p);
                y += node.Size.height + this.options.verticalSeparation;
            }
        },

        layoutRight: function (right) {
            this.setChildrenDirection(this.center, "Right", false);
            this.setChildrenLayout(this.center, "Default", false);
            var h = 0, w = 0, y, i, node;
            for (i = 0; i < right.length; i++) {
                node = right[i];
                node.TreeDirection = "Right";
                var s = this.measure(node, Size.Empty);
                w = Math.max(w, s.Width);
                h += s.height + this.options.verticalSeparation;
            }

            h -= this.options.verticalSeparation;
            var x = this.center.x + this.options.horizontalSeparation + this.center.width;
            y = this.center.y + ((this.center.height - h) / 2);
            for (i = 0; i < right.length; i++) {
                node = right[i];
                var p = new Point(x, y);
                this.arrange(node, p);
                y += node.Size.height + this.options.verticalSeparation;
            }
        },

        layoutUp: function (up) {
            this.setChildrenDirection(this.center, "Up", false);
            this.setChildrenLayout(this.center, "Default", false);
            var w = 0, y, node, i;
            for (i = 0; i < up.length; i++) {
                node = up[i];
                node.TreeDirection = "Up";
                var s = this.measure(node, Size.Empty);
                w += s.width + this.options.horizontalSeparation;
            }

            w -= this.options.horizontalSeparation;
            var x = this.center.x + (this.center.width / 2) - (w / 2);

            // y = this.center.y -verticalSeparation -this.center.height/2 - h;
            for (i = 0; i < up.length; i++) {
                node = up[i];
                y = this.center.y - this.options.verticalSeparation - node.Size.height;
                var p = new Point(x, y);
                this.arrange(node, p);
                x += node.Size.width + this.options.horizontalSeparation;
            }
        },

        layoutDown: function (down) {
            var node, i;
            this.setChildrenDirection(this.center, "Down", false);
            this.setChildrenLayout(this.center, "Default", false);
            var w = 0, y;
            for (i = 0; i < down.length; i++) {
                node = down[i];
                node.treeDirection = "Down";
                var s = this.measure(node, Size.Empty);
                w += s.width + this.options.horizontalSeparation;
            }

            w -= this.options.horizontalSeparation;
            var x = this.center.x + (this.center.width / 2) - (w / 2);
            y = this.center.y + this.options.verticalSeparation + this.center.height;
            for (i = 0; i < down.length; i++) {
                node = down[i];
                var p = new Point(x, y);
                this.arrange(node, p);
                x += node.Size.width + this.options.horizontalSeparation;
            }
        },

        layoutRadialTree: function () {
            // var rmax = children.Aggregate(0D, (current, node) => Math.max(node.SectorAngle, current));
            this.setChildrenDirection(this.center, "Radial", false);
            this.setChildrenLayout(this.center, "Default", false);
            this.previousRoot = null;
            var startAngle = this.options.startRadialAngle * DEG_TO_RAD;
            var endAngle = this.options.endRadialAngle * DEG_TO_RAD;
            if (endAngle <= startAngle) {
                throw "Final angle should not be less than the start angle.";
            }

            this.maxDepth = 0;
            this.origin = new Point(this.center.x, this.center.y);
            this.calculateAngularWidth(this.center, 0);

            // perform the layout
            if (this.maxDepth > 0) {
                this.radialLayout(this.center, this.options.radialFirstLevelSeparation, startAngle, endAngle);
            }

            // update properties of the root node
            this.center.Angle = endAngle - startAngle;
        },

        tipOverTree: function (down, startFromLevel) {
            if (Utils.isUndefined(startFromLevel)) {
                startFromLevel = 0;
            }

            this.setChildrenDirection(this.center, "Down", false);
            this.setChildrenLayout(this.center, "Default", false);
            this.setChildrenLayout(this.center, "Underneath", false, startFromLevel);
            var w = 0, y, node, i;
            for (i = 0; i < down.length; i++) {
                node = down[i];

                // if (node.IsSpecial) continue;
                node.TreeDirection = "Down";
                var s = this.measure(node, Size.Empty);
                w += s.width + this.options.horizontalSeparation;
            }

            w -= this.options.horizontalSeparation;

            // putting the root in the center with respect to the whole diagram is not a nice result, let's put it with respect to the first level only
            w -= down[down.length - 1].width;
            w += down[down.length - 1].associatedShape.bounds().width;

            var x = this.center.x + (this.center.width / 2) - (w / 2);
            y = this.center.y + this.options.verticalSeparation + this.center.height;
            for (i = 0; i < down.length; i++) {
                node = down[i];
                // if (node.IsSpecial) continue;
                var p = new Point(x, y);
                this.arrange(node, p);
                x += node.Size.width + this.options.horizontalSeparation;
            }

            /*//let's place the special node, assuming there is only one
            if (down.Count(n => n.IsSpecial) > 0)
            {
            var special = (from n in down where n.IsSpecial select n).First();
            if (special.Children.Count > 0)
            throw new DiagramException("The 'special' element should not have children.");
            special.Data.Location = new Point(Center.Data.Location.X + Center.AssociatedShape.BoundingRectangle.Width + this.options.HorizontalSeparation, Center.Data.Location.Y);
            }*/
        },
        calculateAngularWidth: function (n, d) {
            if (d > this.maxDepth) {
                this.maxDepth = d;
            }

            var aw = 0, w = 1000, h = 1000, diameter = d === 0 ? 0 : Math.sqrt((w * w) + (h * h)) / d;

            if (n.children.length > 0) {
                // eventually with n.IsExpanded
                for (var i = 0, len = n.children.length; i < len; i++) {
                    var child = n.children[i];
                    aw += this.calculateAngularWidth(child, d + 1);
                }
                aw = Math.max(diameter, aw);
            }
            else {
                aw = diameter;
            }

            n.sectorAngle = aw;
            return aw;
        },
        sortChildren: function (n) {
            var basevalue = 0, i;

            // update basevalue angle for node ordering
            if (n.parents.length > 1) {
                throw "Node is not part of a tree.";
            }
            var p = n.parents[0];
            if (p) {
                var pl = new Point(p.x, p.y);
                var nl = new Point(n.x, n.y);
                basevalue = this.normalizeAngle(Math.atan2(pl.y - nl.y, pl.x - nl.x));
            }

            var count = n.children.length;
            if (count === 0) {
                return null;
            }

            var angle = [];
            var idx = [];

            for (i = 0; i < count; ++i) {
                var c = n.children[i];
                var l = new Point(c.x, c.y);
                idx[i] = i;
                angle[i] = this.normalizeAngle(-basevalue + Math.atan2(l.y - l.y, l.x - l.x));
            }

            Utils.bisort(angle, idx);
            var col = []; // list of nodes
            var children = n.children;
            for (i = 0; i < count; ++i) {
                col.push(children[idx[i]]);
            }

            return col;
        },

        normalizeAngle: function (angle) {
            while (angle > Math.PI * 2) {
                angle -= 2 * Math.PI;
            }
            while (angle < 0) {
                angle += Math.PI * 2;
            }
            return angle;
        },
        radialLayout: function (node, radius, startAngle, endAngle) {
            var deltaTheta = endAngle - startAngle;
            var deltaThetaHalf = deltaTheta / 2.0;
            var parentSector = node.sectorAngle;
            var fraction = 0;
            var sorted = this.sortChildren(node);
            for (var i = 0, len = sorted.length; i < len; i++) {
                var childNode = sorted[i];
                var cp = childNode;
                var childAngleFraction = cp.sectorAngle / parentSector;
                if (childNode.children.length > 0) {
                    this.radialLayout(childNode,
                        radius + this.options.radialSeparation,
                        startAngle + (fraction * deltaTheta),
                        startAngle + ((fraction + childAngleFraction) * deltaTheta));
                }

                this.setPolarLocation(childNode, radius, startAngle + (fraction * deltaTheta) + (childAngleFraction * deltaThetaHalf));
                cp.angle = childAngleFraction * deltaTheta;
                fraction += childAngleFraction;
            }
        },
        setPolarLocation: function (node, radius, angle) {
            node.x = this.origin.x + (radius * Math.cos(angle));
            node.y = this.origin.y + (radius * Math.sin(angle));
            node.BoundingRectangle = new Rect(node.x, node.y, node.width, node.height);
        },

        /**
        * Sets the children direction recursively.
        * @param node
        * @param direction
        * @param includeStart
        */
        setChildrenDirection: function (node, direction, includeStart) {
            var rootDirection = node.treeDirection;
            this.graph.depthFirstTraversal(node, function (n) {
                n.treeDirection = direction;
            });
            if (!includeStart) {
                node.treeDirection = rootDirection;
            }
        },

        /**
        * Sets the children layout recursively.
        * @param node
        * @param layout
        * @param includeStart
        * @param startFromLevel
        */
        setChildrenLayout: function (node, layout, includeStart, startFromLevel) {
            if (Utils.isUndefined(startFromLevel)) {
                startFromLevel = 0;
            }
            var rootLayout = node.childrenLayout;
            if (startFromLevel > 0) {
                // assign levels to the Node.Level property
                this.graph.assignLevels(node);

                // assign the layout on the condition that the level is at least the 'startFromLevel'
                this.graph.depthFirstTraversal(
                    node, function (s) {
                        if (s.level >= startFromLevel + 1) {
                            s.childrenLayout = layout;
                        }
                    }
                );
            }
            else {
                this.graph.depthFirstTraversal(node, function (s) {
                    s.childrenLayout = layout;
                });

                // if the start should not be affected we put the state back
                if (!includeStart) {
                    node.childrenLayout = rootLayout;
                }
            }
        },

        /**
        * Returns the actual size of the node. The given size is the allowed space wherein the node can lay out itself.
        * @param node
        * @param givenSize
        * @returns {Size}
        */
        measure: function (node, givenSize) {
            var w = 0, h = 0, s;
            var result = new Size(0, 0);
            if (!node) {
                throw "";
            }
            var b = node.associatedShape.bounds();
            var shapeWidth = b.width;
            var shapeHeight = b.height;
            if (node.parents.length !== 1) {
                throw "Node not in a spanning tree.";
            }

            var parent = node.parents[0];
            if (node.treeDirection === "Undefined") {
                node.treeDirection = parent.treeDirection;
            }

            if (Utils.isEmpty(node.children)) {
                result = new Size(
                    Math.abs(shapeWidth) < EPSILON ? 50 : shapeWidth,
                    Math.abs(shapeHeight) < EPSILON ? 25 : shapeHeight);
            }
            else if (node.children.length === 1) {
                switch (node.treeDirection) {
                    case "Radial":
                        s = this.measure(node.children[0], givenSize); // child size
                        w = shapeWidth + (this.options.radialSeparation * Math.cos(node.AngleToParent)) + s.width;
                        h = shapeHeight + Math.abs(this.options.radialSeparation * Math.sin(node.AngleToParent)) + s.height;
                        break;
                    case "Left":
                    case "Right":
                        switch (node.childrenLayout) {

                            case "TopAlignedWithParent":
                                break;

                            case "BottomAlignedWithParent":
                                break;

                            case "Underneath":
                                s = this.measure(node.children[0], givenSize);
                                w = shapeWidth + s.width + this.options.underneathHorizontalOffset;
                                h = shapeHeight + this.options.underneathVerticalTopOffset + s.height;
                                break;

                            case "Default":
                                s = this.measure(node.children[0], givenSize);
                                w = shapeWidth + this.options.horizontalSeparation + s.width;
                                h = Math.max(shapeHeight, s.height);
                                break;

                            default:
                                throw "Unhandled TreeDirection in the Radial layout measuring.";
                        }
                        break;
                    case "Up":
                    case "Down":
                        switch (node.childrenLayout) {

                            case "TopAlignedWithParent":
                            case "BottomAlignedWithParent":
                                break;

                            case "Underneath":
                                s = this.measure(node.children[0], givenSize);
                                w = Math.max(shapeWidth, s.width + this.options.underneathHorizontalOffset);
                                h = shapeHeight + this.options.underneathVerticalTopOffset + s.height;
                                break;

                            case "Default":
                                s = this.measure(node.children[0], givenSize);
                                h = shapeHeight + this.options.verticalSeparation + s.height;
                                w = Math.max(shapeWidth, s.width);
                                break;

                            default:
                                throw "Unhandled TreeDirection in the Down layout measuring.";
                        }
                        break;
                    default:
                        throw "Unhandled TreeDirection in the layout measuring.";
                }

                result = new Size(w, h);
            }
            else {
                var i, childNode;
                switch (node.treeDirection) {
                    case "Left":
                    case "Right":
                        switch (node.childrenLayout) {

                            case "TopAlignedWithParent":
                            case "BottomAlignedWithParent":
                                break;

                            case "Underneath":
                                w = shapeWidth;
                                h = shapeHeight + this.options.underneathVerticalTopOffset;
                                for (i = 0; i < node.children.length; i++) {
                                    childNode = node.children[i];
                                    s = this.measure(childNode, givenSize);
                                    w = Math.max(w, s.width + this.options.underneathHorizontalOffset);
                                    h += s.height + this.options.underneathVerticalSeparation;
                                }

                                h -= this.options.underneathVerticalSeparation;
                                break;

                            case "Default":
                                w = shapeWidth;
                                h = 0;
                                for (i = 0; i < node.children.length; i++) {
                                    childNode = node.children[i];
                                    s = this.measure(childNode, givenSize);
                                    w = Math.max(w, shapeWidth + this.options.horizontalSeparation + s.width);
                                    h += s.height + this.options.verticalSeparation;
                                }
                                h -= this.options.verticalSeparation;
                                break;

                            default:
                                throw "Unhandled TreeDirection in the Right layout measuring.";
                        }

                        break;
                    case "Up":
                    case "Down":

                        switch (node.childrenLayout) {

                            case "TopAlignedWithParent":
                            case "BottomAlignedWithParent":
                                break;

                            case "Underneath":
                                w = shapeWidth;
                                h = shapeHeight + this.options.underneathVerticalTopOffset;
                                for (i = 0; i < node.children.length; i++) {
                                    childNode = node.children[i];
                                    s = this.measure(childNode, givenSize);
                                    w = Math.max(w, s.width + this.options.underneathHorizontalOffset);
                                    h += s.height + this.options.underneathVerticalSeparation;
                                }

                                h -= this.options.underneathVerticalSeparation;
                                break;

                            case "Default":
                                w = 0;
                                h = 0;
                                for (i = 0; i < node.children.length; i++) {
                                    childNode = node.children[i];
                                    s = this.measure(childNode, givenSize);
                                    w += s.width + this.options.horizontalSeparation;
                                    h = Math.max(h, s.height + this.options.verticalSeparation + shapeHeight);
                                }

                                w -= this.options.horizontalSeparation;
                                break;

                            default:
                                throw "Unhandled TreeDirection in the Down layout measuring.";
                        }

                        break;
                    default:
                        throw "Unhandled TreeDirection in the layout measuring.";
                }

                result = new Size(w, h);
            }

            node.SectorAngle = Math.sqrt((w * w / 4) + (h * h / 4));
            node.Size = result;
            return result;
        },
        arrange: function (n, p) {
            var i, pp, child, node, childrenwidth, b = n.associatedShape.bounds();
            var shapeWidth = b.width;
            var shapeHeight = b.height;
            if (Utils.isEmpty(n.children)) {
                n.x = p.x;
                n.y = p.y;
                n.BoundingRectangle = new Rect(p.x, p.y, shapeWidth, shapeHeight);
            }
            else {
                var x, y;
                var selfLocation;
                switch (n.treeDirection) {
                    case "Left":
                        switch (n.childrenLayout) {
                            case "TopAlignedWithParent":
                            case "BottomAlignedWithParent":
                                break;

                            case "Underneath":
                                selfLocation = p;
                                n.x = selfLocation.x;
                                n.y = selfLocation.y;
                                n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
                                y = p.y + shapeHeight + this.options.underneathVerticalTopOffset;
                                for (i = 0; i < node.children.length; i++) {
                                    node = node.children[i];
                                    x = selfLocation.x - node.associatedShape.width - this.options.underneathHorizontalOffset;
                                    pp = new Point(x, y);
                                    this.arrange(node, pp);
                                    y += node.Size.height + this.options.underneathVerticalSeparation;
                                }
                                break;

                            case "Default":
                                selfLocation = new Point(p.x + n.Size.width - shapeWidth, p.y + ((n.Size.height - shapeHeight) / 2));
                                n.x = selfLocation.x;
                                n.y = selfLocation.y;
                                n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
                                x = selfLocation.x - this.options.horizontalSeparation; // alignment of children
                                y = p.y;
                                for (i = 0; i < n.children.length; i++) {
                                    node = n.children[i];
                                    pp = new Point(x - node.Size.width, y);
                                    this.arrange(node, pp);
                                    y += node.Size.height + this.options.verticalSeparation;
                                }
                                break;

                            default:
                                throw "Unsupported TreeDirection";
                        }

                        break;
                    case "Right":
                        switch (n.childrenLayout) {
                            case "TopAlignedWithParent":
                            case "BottomAlignedWithParent":
                                break;

                            case "Underneath":
                                selfLocation = p;
                                n.x = selfLocation.x;
                                n.y = selfLocation.y;
                                n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
                                x = p.x + shapeWidth + this.options.underneathHorizontalOffset;

                                // alignment of children left-underneath the parent
                                y = p.y + shapeHeight + this.options.underneathVerticalTopOffset;
                                for (i = 0; i < n.children.length; i++) {
                                    node = n.children[i];
                                    pp = new Point(x, y);
                                    this.arrange(node, pp);
                                    y += node.Size.height + this.options.underneathVerticalSeparation;
                                }

                                break;

                            case "Default":
                                selfLocation = new Point(p.x, p.y + ((n.Size.height - shapeHeight) / 2));
                                n.x = selfLocation.x;
                                n.y = selfLocation.y;
                                n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
                                x = p.x + shapeWidth + this.options.horizontalSeparation; // alignment of children
                                y = p.y;
                                for (i = 0; i < n.children.length; i++) {
                                    node = n.children[i];
                                    pp = new Point(x, y);
                                    this.arrange(node, pp);
                                    y += node.Size.height + this.options.verticalSeparation;
                                }
                                break;

                            default:
                                throw "Unsupported TreeDirection";
                        }

                        break;
                    case "Up":
                        selfLocation = new Point(p.x + ((n.Size.width - shapeWidth) / 2), p.y + n.Size.height - shapeHeight);
                        n.x = selfLocation.x;
                        n.y = selfLocation.y;
                        n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
                        if (Math.abs(selfLocation.x - p.x) < EPSILON) {
                            childrenwidth = 0;
                            // means there is an aberration due to the oversized Element with respect to the children
                            for (i = 0; i < n.children.length; i++) {
                                child = n.children[i];
                                childrenwidth += child.Size.width + this.options.horizontalSeparation;
                            }
                            childrenwidth -= this.options.horizontalSeparation;
                            x = p.x + ((shapeWidth - childrenwidth) / 2);
                        }
                        else {
                            x = p.x;
                        }

                        for (i = 0; i < n.children.length; i++) {
                            node = n.children[i];
                            y = selfLocation.y - this.options.verticalSeparation - node.Size.height;
                            pp = new Point(x, y);
                            this.arrange(node, pp);
                            x += node.Size.width + this.options.horizontalSeparation;
                        }
                        break;

                    case "Down":

                        switch (n.childrenLayout) {
                            case "TopAlignedWithParent":
                            case "BottomAlignedWithParent":
                                break;
                            case "Underneath":
                                selfLocation = p;
                                n.x = selfLocation.x;
                                n.y = selfLocation.y;
                                n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
                                x = p.x + this.options.underneathHorizontalOffset; // alignment of children left-underneath the parent
                                y = p.y + shapeHeight + this.options.underneathVerticalTopOffset;
                                for (i = 0; i < n.children.length; i++) {
                                    node = n.children[i];
                                    pp = new Point(x, y);
                                    this.arrange(node, pp);
                                    y += node.Size.height + this.options.underneathVerticalSeparation;
                                }
                                break;

                            case "Default":
                                selfLocation = new Point(p.x + ((n.Size.width - shapeWidth) / 2), p.y);
                                n.x = selfLocation.x;
                                n.y = selfLocation.y;
                                n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
                                if (Math.abs(selfLocation.x - p.x) < EPSILON) {
                                    childrenwidth = 0;
                                    // means there is an aberration due to the oversized Element with respect to the children
                                    for (i = 0; i < n.children.length; i++) {
                                        child = n.children[i];
                                        childrenwidth += child.Size.width + this.options.horizontalSeparation;
                                    }

                                    childrenwidth -= this.options.horizontalSeparation;
                                    x = p.x + ((shapeWidth - childrenwidth) / 2);
                                }
                                else {
                                    x = p.x;
                                }

                                for (i = 0; i < n.children.length; i++) {
                                    node = n.children[i];
                                    y = selfLocation.y + this.options.verticalSeparation + shapeHeight;
                                    pp = new Point(x, y);
                                    this.arrange(node, pp);
                                    x += node.Size.width + this.options.horizontalSeparation;
                                }
                                break;

                            default:
                                throw "Unsupported TreeDirection";
                        }
                        break;

                    case "None":
                        break;

                    default:
                        throw "Unsupported TreeDirection";
                }
            }
        },
        layoutSwitch: function () {
            if (!this.center) {
                return;
            }

            if (Utils.isEmpty(this.center.children)) {
                return;
            }

            var type = this.options.subtype;
            if (Utils.isUndefined(type)) {
                type = "Down";
            }
            var single, male, female, leftcount;
            var children = this.center.children;
            switch (type.toLowerCase()) {
                case "radial":
                case "radialtree":
                    this.layoutRadialTree();
                    break;

                case "mindmaphorizontal":
                case "mindmap":
                    single = this.center.children;

                    if (this.center.children.length === 1) {
                        this.layoutRight(single);
                    }
                    else {
                        // odd number will give one more at the right
                        leftcount = children.length / 2;
                        male = grep(this.center.children, function (n) {
                            return Utils.indexOf(children, n) < leftcount;
                        });
                        female = grep(this.center.children, function (n) {
                            return Utils.indexOf(children, n) >= leftcount;
                        });

                        this.layoutLeft(male);
                        this.layoutRight(female);
                    }
                    break;

                case "mindmapvertical":
                    single = this.center.children;

                    if (this.center.children.length === 1) {
                        this.layoutDown(single);
                    }
                    else {
                        // odd number will give one more at the right
                        leftcount = children.length / 2;
                        male = grep(this.center.children, function (n) {
                            return Utils.indexOf(children, n) < leftcount;
                        });
                        female = grep(this.center.children, function (n) {
                            return Utils.indexOf(children, n) >= leftcount;
                        });
                        this.layoutUp(male);
                        this.layoutDown(female);
                    }
                    break;

                case "right":
                    this.layoutRight(this.center.children);
                    break;

                case "left":
                    this.layoutLeft(this.center.children);
                    break;

                case "up":
                case "bottom":
                    this.layoutUp(this.center.children);
                    break;

                case "down":
                case "top":
                    this.layoutDown(this.center.children);
                    break;

                case "tipover":
                case "tipovertree":
                    if (this.options.tipOverTreeStartLevel < 0) {
                        throw "The tip-over level should be a positive integer.";
                    }
                    this.tipOverTree(this.center.children, this.options.tipOverTreeStartLevel);
                    break;

                case "undefined":
                case "none":
                    break;
            }
        }
    });

    /**
    * The various tree layout algorithms.
    * @type {*}
    */
    var TreeLayout = LayoutBase.extend({
        init: function (diagram) {
            var that = this;
            LayoutBase.fn.init.call(that);
            if (Utils.isUndefined(diagram)) {
                throw "No diagram specified.";
            }
            this.diagram = diagram;
        },

        /**
        * Arranges the diagram in a tree-layout with the specified options and tree subtype.
        */
        layout: function (options) {

            this.transferOptions(options);

            // transform the diagram into a Graph
            var adapter = new DiagramToHyperTreeAdapter(this.diagram);

            /**
            * The Graph reduction from the given diagram.
            * @type {*}
            */
            this.graph = adapter.convert();

            var finalNodeSet = this.layoutComponents();

            // note that the graph contains the original data and
            // the components are another instance of nodes referring to the same set of shapes
            return new diagram.LayoutState(this.diagram, finalNodeSet);
        },

        layoutComponents: function () {
            if (this.graph.isEmpty()) {
                return;
            }

            // split into connected components
            var components = this.graph.getConnectedComponents();
            if (Utils.isEmpty(components)) {
                return;
            }

            var layout = new TreeLayoutProcessor(this.options);
            var trees = [];
            // find a spanning tree for each component
            for (var i = 0; i < components.length; i++) {
                var component = components[i];

                var treeGraph = this.getTree(component);
                if (!treeGraph) {
                    throw "Failed to find a spanning tree for the component.";
                }
                var root = treeGraph.root;
                var tree = treeGraph.tree;
                layout.layout(tree, root);

                trees.push(tree);
            }

            return this.gridLayoutComponents(trees);

        },

        /**
        * Gets a spanning tree (and root) for the given graph.
        * Ensure that the given graph is connected!
        * @param graph
        * @returns {*} A literal object consisting of the found root and the spanning tree.
        */
        getTree: function (graph) {
            var root = null;
            if (this.options.roots && this.options.roots.length > 0) {
                for (var i = 0, len = graph.nodes.length; i < len; i++) {
                    var node = graph.nodes[i];
                    for (var j = 0; j < this.options.roots.length; j++) {
                        var givenRootShape = this.options.roots[j];
                        if (givenRootShape === node.associatedShape) {
                            root = node;
                            break;
                        }
                    }
                }
            }
            if (!root) {
                // finds the most probable root on the basis of the longest path in the component
                root = graph.root();
                // should not happen really
                if (!root) {
                    throw "Unable to find a root for the tree.";
                }
            }
            return this.getTreeForRoot(graph, root);
        },

        getTreeForRoot: function (graph, root) {

            var tree = graph.getSpanningTree(root);
            if (Utils.isUndefined(tree) || tree.isEmpty()) {
                return null;
            }
            return {
                tree: tree,
                root: tree.root
            };
        }

    });

    /**
    * The Sugiyama aka layered layout algorithm.
    * @type {*}
    */
    var LayeredLayout = LayoutBase.extend({
        init: function (diagram) {
            var that = this;
            LayoutBase.fn.init.call(that);
            if (Utils.isUndefined(diagram)) {
                throw "Diagram is not specified.";
            }
            this.diagram = diagram;
        },

        layout: function (options) {

            this.transferOptions(options);

            var adapter = new DiagramToHyperTreeAdapter(this.diagram);
            var graph = adapter.convert(options);
            if (graph.isEmpty()) {
                return;
            }
            // split into connected components
            var components = graph.getConnectedComponents();
            if (Utils.isEmpty(components)) {
                return;
            }
            for (var i = 0; i < components.length; i++) {
                var component = components[i];
                this.layoutGraph(component, options);
            }
            var finalNodeSet = this.gridLayoutComponents(components);
            return new diagram.LayoutState(this.diagram, finalNodeSet);

        },

        /**
        * Initializes the runtime data properties of the layout.
        * @private
        */
        _initRuntimeProperties: function () {
            for (var k = 0; k < this.graph.nodes.length; k++) {
                var node = this.graph.nodes[k];
                node.layer = -1;
                node.downstreamLinkCount = 0;
                node.upstreamLinkCount = 0;

                node.isVirtual = false;

                node.uBaryCenter = 0.0;
                node.dBaryCenter = 0.0;

                node.upstreamPriority = 0;
                node.downstreamPriority = 0;

                node.gridPosition = 0;
            }
        },
        _prepare: function (graph) {
            var current = [], i, l, link;
            for (l = 0; l < graph.links.length; l++) {
                // of many dummies have been inserted to make things work
                graph.links[l].depthOfDumminess = 0;
            }

            // defines a mapping of a node to the layer index
            var layerMap = new Dictionary();

            Utils.forEach(graph.nodes, function (node) {
                if (node.incoming.length === 0) {
                    layerMap.set(node, 0);
                    current.push(node);
                }
            });

            while (current.length > 0) {
                var next = current.shift();
                for (i = 0; i < next.outgoing.length; i++) {
                    link = next.outgoing[i];
                    var target = link.target;

                    if (layerMap.containsKey(target)) {
                        layerMap.set(target, Math.max(layerMap.get(next) + 1, layerMap.get(target)));
                    } else {
                        layerMap.set(target, layerMap.get(next) + 1);
                    }

                    if (!contains(current, target)) {
                        current.push(target);
                    }
                }
            }

            // the node count in the map defines how many layers w'll need
            var layerCount = 0;
            layerMap.forEachValue(function (nodecount) {
                layerCount = Math.max(layerCount, nodecount);
            });

            var sortedNodes = [];
            Utils.addRange(sortedNodes, layerMap.keys());
            sortedNodes.sort(function (o1, o2) {
                var o1layer = layerMap.get(o1);
                var o2layer = layerMap.get(o2);
                return Utils.sign(o2layer - o1layer);
            });

            for (var n = 0; n < sortedNodes.length; ++n) {
                var node = sortedNodes[n];
                var minLayer = Number.MAX_VALUE;

                if (node.outgoing.length === 0) {
                    continue;
                }

                for (l = 0; l < node.outgoing.length; ++l) {
                    link = node.outgoing[l];
                    minLayer = Math.min(minLayer, layerMap.get(link.target));
                }

                if (minLayer > 1) {
                    layerMap.set(node, minLayer - 1);
                }
            }

            this.layers = [];
            for (i = 0; i < layerCount + 1; i++) {
                this.layers.push([]);
            }

            layerMap.forEach(function (node, layer) {
                node.layer = layer;
                this.layers[layer].push(node);
            }, this);

            // set initial grid positions
            for (l = 0; l < this.layers.length; l++) {
                var layer = this.layers[l];
                for (i = 0; i < layer.length; i++) {
                    layer[i].gridPosition = i;
                }
            }
        },

        /**
        * Performs the layout of a single component.
        */
        layoutGraph: function (graph, options) {
            if (Utils.isUndefined(graph)) {
                throw "No graph given or graph analysis of the diagram failed.";
            }
            if (Utils.isDefined(options)) {
                this.transferOptions(options);
            }
            this.graph = graph;

            // sets unique indices on the nodes
            graph.setItemIndices();

            // ensures no cycles present for this layout
            var reversedEdges = graph.makeAcyclic();

            // define the runtime props being used by the layout algorithm
            this._initRuntimeProperties();

            this._prepare(graph, options);

            this._dummify();

            this._optimizeCrossings();

            this._swapPairs();

            this.arrangeNodes();

            this._moveThingsAround();

            this._dedummify();

            // re-reverse the links which were switched earlier
            Utils.forEach(reversedEdges, function (e) {
                if (e.points) {
                    e.points.reverse();
                }
            });
        },

        setMinDist: function (m, n, minDist) {
            var l = m.layer;
            var i = m.layerIndex;
            this.minDistances[l][i] = minDist;
        },

        getMinDist: function (m, n) {
            var dist = 0,
                i1 = m.layerIndex,
                i2 = n.layerIndex,
                l = m.layer,
                min = Math.min(i1, i2),
                max = Math.max(i1, i2);
            // use Sum()?
            for (var k = min; k < max; ++k) {
                dist += this.minDistances[l][k];
            }
            return dist;
        },

        placeLeftToRight: function (leftClasses) {
            var leftPos = new Dictionary(), n, node;
            for (var c = 0; c < this.layers.length; ++c) {
                var classNodes = leftClasses[c];
                if (!classNodes) {
                    continue;
                }

                for (n = 0; n < classNodes.length; n++) {
                    node = classNodes[n];
                    if (!leftPos.containsKey(node)) {
                        this.placeLeft(node, leftPos, c);
                    }
                }

                // adjust class
                var d = Number.POSITIVE_INFINITY;
                for (n = 0; n < classNodes.length; n++) {
                    node = classNodes[n];
                    var rightSibling = this.rightSibling(node);
                    if (rightSibling && this.nodeLeftClass.get(rightSibling) !== c) {
                        d = Math.min(d, leftPos.get(rightSibling) - leftPos.get(node) - this.getMinDist(node, rightSibling));
                    }
                }
                if (d === Number.POSITIVE_INFINITY) {
                    var D = [];
                    for (n = 0; n < classNodes.length; n++) {
                        node = classNodes[n];
                        var neighbors = [];
                        Utils.addRange(neighbors, this.upNodes.get(node));
                        Utils.addRange(neighbors, this.downNodes.get(node));

                        for (var e = 0; e < neighbors.length; e++) {
                            var neighbor = neighbors[e];
                            if (this.nodeLeftClass.get(neighbor) < c) {
                                D.push(leftPos.get(neighbor) - leftPos.get(node));
                            }
                        }
                    }
                    D.sort();
                    if (D.length === 0) {
                        d = 0;
                    }
                    else if (D.length % 2 === 1) {
                        d = D[this.intDiv(D.length, 2)];
                    }
                    else {
                        d = (D[this.intDiv(D.length, 2) - 1] + D[this.intDiv(D.length, 2)]) / 2;
                    }
                }
                for (n = 0; n < classNodes.length; n++) {
                    node = classNodes[n];
                    leftPos.set(node, leftPos.get(node) + d);
                }
            }
            return leftPos;
        },

        placeRightToLeft: function (rightClasses) {
            var rightPos = new Dictionary(), n, node;
            for (var c = 0; c < this.layers.length; ++c) {
                var classNodes = rightClasses[c];
                if (!classNodes) {
                    continue;
                }

                for (n = 0; n < classNodes.length; n++) {
                    node = classNodes[n];
                    if (!rightPos.containsKey(node)) {
                        this.placeRight(node, rightPos, c);
                    }
                }

                // adjust class
                var d = Number.NEGATIVE_INFINITY;
                for (n = 0; n < classNodes.length; n++) {
                    node = classNodes[n];
                    var leftSibling = this.leftSibling(node);
                    if (leftSibling && this.nodeRightClass.get(leftSibling) !== c) {
                        d = Math.max(d, rightPos.get(leftSibling) - rightPos.get(node) + this.getMinDist(leftSibling, node));
                    }
                }
                if (d === Number.NEGATIVE_INFINITY) {
                    var D = [];
                    for (n = 0; n < classNodes.length; n++) {
                        node = classNodes[n];
                        var neighbors = [];
                        Utils.addRange(neighbors, this.upNodes.get(node));
                        Utils.addRange(neighbors, this.downNodes.get(node));

                        for (var e = 0; e < neighbors.length; e++) {
                            var neighbor = neighbors[e];
                            if (this.nodeRightClass.get(neighbor) < c) {
                                D.push(rightPos.get(node) - rightPos.get(neighbor));
                            }
                        }
                    }
                    D.sort();
                    if (D.length === 0) {
                        d = 0;
                    }
                    else if (D.length % 2 === 1) {
                        d = D[this.intDiv(D.length, 2)];
                    }
                    else {
                        d = (D[this.intDiv(D.length, 2) - 1] + D[this.intDiv(D.length, 2)]) / 2;
                    }
                }
                for (n = 0; n < classNodes.length; n++) {
                    node = classNodes[n];
                    rightPos.set(node, rightPos.get(node) + d);
                }
            }
            return rightPos;
        },

        _getLeftWing: function () {
            var leftWing = { value: null };
            var result = this.computeClasses(leftWing, 1);
            this.nodeLeftClass = leftWing.value;
            return result;
        },

        _getRightWing: function () {
            var rightWing = { value: null };
            var result = this.computeClasses(rightWing, -1);
            this.nodeRightClass = rightWing.value;
            return result;
        },

        computeClasses: function (wingPair, d) {
            var currentWing = 0,
                wing = wingPair.value = new Dictionary();

            for (var l = 0; l < this.layers.length; ++l) {
                currentWing = l;

                var layer = this.layers[l];
                for (var n = d === 1 ? 0 : layer.length - 1; 0 <= n && n < layer.length; n += d) {
                    var node = layer[n];
                    if (!wing.containsKey(node)) {
                        wing.set(node, currentWing);
                        if (node.isVirtual) {
                            var ndsinl = this._nodesInLink(node);
                            for (var kk = 0; kk < ndsinl.length; kk++) {
                                var vnode = ndsinl[kk];
                                wing.set(vnode, currentWing);
                            }
                        }
                    }
                    else {
                        currentWing = wing.get(node);
                    }
                }
            }

            var wings = [];
            for (var i = 0; i < this.layers.length; i++) {
                wings.push(null);
            }
            wing.forEach(function (node, classIndex) {
                if (wings[classIndex] === null) {
                    wings[classIndex] = [];
                }
                wings[classIndex].push(node);
            });

            return wings;
        },
        _isVerticalLayout: function () {
            return this.options.subtype.toLowerCase() === "up" || this.options.subtype.toLowerCase() === "down" || this.options.subtype.toLowerCase() === "vertical";
        },

        _isHorizontalLayout: function () {
            return this.options.subtype.toLowerCase() === "right" || this.options.subtype.toLowerCase() === "left" || this.options.subtype.toLowerCase() === "horizontal";
        },
        _isIncreasingLayout: function () {
            // meaning that the visiting of the layers goes in the natural order of increasing layer index
            return this.options.subtype.toLowerCase() === "right" || this.options.subtype.toLowerCase() === "down";
        },
        _moveThingsAround: function () {
            var i, l, node, layer, n, w;
            // sort the layers by their grid position
            for (l = 0; l < this.layers.length; ++l) {
                layer = this.layers[l];
                layer.sort(this._gridPositionComparer);
            }

            this.minDistances = [];
            for (l = 0; l < this.layers.length; ++l) {
                layer = this.layers[l];
                this.minDistances[l] = [];
                for (n = 0; n < layer.length; ++n) {
                    node = layer[n];
                    node.layerIndex = n;
                    this.minDistances[l][n] = this.options.nodeDistance;
                    if (n < layer.length - 1) {
                        if (this._isVerticalLayout()) {
                            this.minDistances[l][n] += (node.width + layer[n + 1].width) / 2;
                        }
                        else {
                            this.minDistances[l][n] += (node.height + layer[n + 1].height) / 2;
                        }
                    }
                }
            }

            this.downNodes = new Dictionary();
            this.upNodes = new Dictionary();
            Utils.forEach(this.graph.nodes, function (node) {
                this.downNodes.set(node, []);
                this.upNodes.set(node, []);
            }, this);
            Utils.forEach(this.graph.links, function (link) {
                var origin = link.source;
                var dest = link.target;
                var down = null, up = null;
                if (origin.layer > dest.layer) {
                    down = link.source;
                    up = link.target;
                }
                else {
                    up = link.source;
                    down = link.target;
                }
                this.downNodes.get(up).push(down);
                this.upNodes.get(down).push(up);
            }, this);
            this.downNodes.forEachValue(function (list) {
                list.sort(this._gridPositionComparer);
            }, this);
            this.upNodes.forEachValue(function (list) {
                list.sort(this._gridPositionComparer);
            }, this);

            for (l = 0; l < this.layers.length - 1; ++l) {
                layer = this.layers[l];
                for (w = 0; w < layer.length - 1; w++) {
                    var currentNode = layer[w];
                    if (!currentNode.isVirtual) {
                        continue;
                    }

                    var currDown = this.downNodes.get(currentNode)[0];
                    if (!currDown.isVirtual) {
                        continue;
                    }

                    for (n = w + 1; n < layer.length; ++n) {
                        node = layer[n];
                        if (!node.isVirtual) {
                            continue;
                        }

                        var downNode = this.downNodes.get(node)[0];
                        if (!downNode.isVirtual) {
                            continue;
                        }

                        if (currDown.gridPosition > downNode.gridPosition) {
                            var pos = currDown.gridPosition;
                            currDown.gridPosition = downNode.gridPosition;
                            downNode.gridPosition = pos;
                            var i1 = currDown.layerIndex;
                            var i2 = downNode.layerIndex;
                            this.layers[l + 1][i1] = downNode;
                            this.layers[l + 1][i2] = currDown;
                            currDown.layerIndex = i2;
                            downNode.layerIndex = i1;
                        }
                    }
                }
            }


            var leftClasses = this._getLeftWing();
            var rightClasses = this._getRightWing();


            var leftPos = this.placeLeftToRight(leftClasses);
            var rightPos = this.placeRightToLeft(rightClasses);
            var x = new Dictionary();
            Utils.forEach(this.graph.nodes, function (node) {
                x.set(node, (leftPos.get(node) + rightPos.get(node)) / 2);
            });


            var order = new Dictionary();
            var placed = new Dictionary();
            for (l = 0; l < this.layers.length; ++l) {
                layer = this.layers[l];
                var sequenceStart = -1, sequenceEnd = -1;
                for (n = 0; n < layer.length; ++n) {
                    node = layer[n];
                    order.set(node, 0);
                    placed.set(node, false);
                    if (node.isVirtual) {
                        if (sequenceStart === -1) {
                            sequenceStart = n;
                        }
                        else if (sequenceStart === n - 1) {
                            sequenceStart = n;
                        }
                        else {
                            sequenceEnd = n;
                            order.set(layer[sequenceStart], 0);
                            if (x.get(node) - x.get(layer[sequenceStart]) === this.getMinDist(layer[sequenceStart], node)) {
                                placed.set(layer[sequenceStart], true);
                            }
                            else {
                                placed.set(layer[sequenceStart], false);
                            }
                            sequenceStart = n;
                        }
                    }
                }
            }
            var directions = [1, -1];
            Utils.forEach(directions, function (d) {
                var start = d === 1 ? 0 : this.layers.length - 1;
                for (var l = start; 0 <= l && l < this.layers.length; l += d) {
                    var layer = this.layers[l];
                    var virtualStartIndex = this._firstVirtualNode(layer);
                    var virtualStart = null;
                    var sequence = null;
                    if (virtualStartIndex !== -1) {
                        virtualStart = layer[virtualStartIndex];
                        sequence = [];
                        for (i = 0; i < virtualStartIndex; i++) {
                            sequence.push(layer[i]);
                        }
                    }
                    else {
                        virtualStart = null;
                        sequence = layer;
                    }
                    if (sequence.length > 0) {
                        this._sequencer(x, null, virtualStart, d, sequence);
                        for (i = 0; i < sequence.length - 1; ++i) {
                            this.setMinDist(sequence[i], sequence[i + 1], x.get(sequence[i + 1]) - x.get(sequence[i]));
                        }
                        if (virtualStart) {
                            this.setMinDist(sequence[sequence.length - 1], virtualStart, x.get(virtualStart) - x.get(sequence[sequence.length - 1]));
                        }
                    }

                    while (virtualStart) {
                        var virtualEnd = this.nextVirtualNode(layer, virtualStart);
                        if (!virtualEnd) {
                            virtualStartIndex = virtualStart.layerIndex;
                            sequence = [];
                            for (i = virtualStartIndex + 1; i < layer.length; i++) {
                                sequence.push(layer[i]);
                            }
                            if (sequence.length > 0) {
                                this._sequencer(x, virtualStart, null, d, sequence);
                                for (i = 0; i < sequence.length - 1; ++i) {
                                    this.setMinDist(sequence[i], sequence[i + 1], x.get(sequence[i + 1]) - x.get(sequence[i]));
                                }
                                this.setMinDist(virtualStart, sequence[0], x.get(sequence[0]) - x.get(virtualStart));
                            }
                        }
                        else if (order.get(virtualStart) === d) {
                            virtualStartIndex = virtualStart.layerIndex;
                            var virtualEndIndex = virtualEnd.layerIndex;
                            sequence = [];
                            for (i = virtualStartIndex + 1; i < virtualEndIndex; i++) {
                                sequence.push(layer[i]);
                            }
                            if (sequence.length > 0) {
                                this._sequencer(x, virtualStart, virtualEnd, d, sequence);
                            }
                            placed.set(virtualStart, true);
                        }
                        virtualStart = virtualEnd;
                    }
                    this.adjustDirections(l, d, order, placed);
                }
            }, this);


            var fromLayerIndex = this._isIncreasingLayout() ? 0 : this.layers.length - 1;
            var reachedFinalLayerIndex = function (k, ctx) {
                if (ctx._isIncreasingLayout()) {
                    return k < ctx.layers.length;
                }
                else {
                    return k >= 0;
                }
            };
            var layerIncrement = this._isIncreasingLayout() ? +1 : -1, offset = 0;

            /**
            * Calcs the max height of the given layer.
            */
            function maximumHeight(layer, ctx) {
                var height = Number.MIN_VALUE;
                for (var n = 0; n < layer.length; ++n) {
                    var node = layer[n];
                    if (ctx._isVerticalLayout()) {
                        height = Math.max(height, node.height);
                    }
                    else {
                        height = Math.max(height, node.width);
                    }
                }
                return height;
            }

            for (i = fromLayerIndex; reachedFinalLayerIndex(i, this); i += layerIncrement) {
                layer = this.layers[i];
                var height = maximumHeight(layer, this);

                for (n = 0; n < layer.length; ++n) {
                    node = layer[n];
                    if (this._isVerticalLayout()) {
                        node.x = x.get(node);
                        node.y = offset + height / 2;
                    }
                    else {
                        node.x = offset + height / 2;
                        node.y = x.get(node);
                    }
                }

                offset += this.options.layerSeparation + height;
            }
        },

        adjustDirections: function (l, d, order, placed) {
            if (l + d < 0 || l + d >= this.layers.length) {
                return;
            }

            var prevBridge = null, prevBridgeTarget = null;
            var layer = this.layers[l + d];
            for (var n = 0; n < layer.length; ++n) {
                var nextBridge = layer[n];
                if (nextBridge.isVirtual) {
                    var nextBridgeTarget = this.getNeighborOnLayer(nextBridge, l);
                    if (nextBridgeTarget.isVirtual) {
                        if (prevBridge) {
                            var p = placed.get(prevBridgeTarget);
                            var clayer = this.layers[l];
                            var i1 = prevBridgeTarget.layerIndex;
                            var i2 = nextBridgeTarget.layerIndex;
                            for (var i = i1 + 1; i < i2; ++i) {
                                if (clayer[i].isVirtual) {
                                    p = p && placed.get(clayer[i]);
                                }
                            }
                            if (p) {
                                order.set(prevBridge, d);
                                var j1 = prevBridge.layerIndex;
                                var j2 = nextBridge.layerIndex;
                                for (var j = j1 + 1; j < j2; ++j) {
                                    if (layer[j].isVirtual) {
                                        order.set(layer[j], d);
                                    }
                                }
                            }
                        }
                        prevBridge = nextBridge;
                        prevBridgeTarget = nextBridgeTarget;
                    }
                }
            }
        },

        getNeighborOnLayer: function (node, l) {
            var neighbor = this.upNodes.get(node)[0];
            if (neighbor.layer === l) {
                return neighbor;
            }
            neighbor = this.downNodes.get(node)[0];
            if (neighbor.layer === l) {
                return neighbor;
            }
            return null;
        },

        _sequencer: function (x, virtualStart, virtualEnd, dir, sequence) {
            if (sequence.length === 1) {
                this._sequenceSingle(x, virtualStart, virtualEnd, dir, sequence[0]);
            }

            if (sequence.length > 1) {
                var r = sequence.length, t = this.intDiv(r, 2);
                this._sequencer(x, virtualStart, virtualEnd, dir, sequence.slice(0, t));
                this._sequencer(x, virtualStart, virtualEnd, dir, sequence.slice(t));
                this.combineSequences(x, virtualStart, virtualEnd, dir, sequence);
            }
        },

        _sequenceSingle: function (x, virtualStart, virtualEnd, dir, node) {
            var neighbors = dir === -1 ? this.downNodes.get(node) : this.upNodes.get(node);

            var n = neighbors.length;
            if (n !== 0) {
                if (n % 2 === 1) {
                    x.set(node, x.get(neighbors[this.intDiv(n, 2)]));
                }
                else {
                    x.set(node, (x.get(neighbors[this.intDiv(n, 2) - 1]) + x.get(neighbors[this.intDiv(n, 2)])) / 2);
                }

                if (virtualStart) {
                    x.set(node, Math.max(x.get(node), x.get(virtualStart) + this.getMinDist(virtualStart, node)));
                }
                if (virtualEnd) {
                    x.set(node, Math.min(x.get(node), x.get(virtualEnd) - this.getMinDist(node, virtualEnd)));
                }
            }
        },

        combineSequences: function (x, virtualStart, virtualEnd, dir, sequence) {
            var r = sequence.length, t = this.intDiv(r, 2);

            // collect left changes
            var leftHeap = [], i, c, n, neighbors, neighbor, pair;
            for (i = 0; i < t; ++i) {
                c = 0;
                neighbors = dir === -1 ? this.downNodes.get(sequence[i]) : this.upNodes.get(sequence[i]);
                for (n = 0; n < neighbors.length; ++n) {
                    neighbor = neighbors[n];
                    if (x.get(neighbor) >= x.get(sequence[i])) {
                        c++;
                    }
                    else {
                        c--;
                        leftHeap.push({ k: x.get(neighbor) + this.getMinDist(sequence[i], sequence[t - 1]), v: 2 });
                    }
                }
                leftHeap.push({ k: x.get(sequence[i]) + this.getMinDist(sequence[i], sequence[t - 1]), v: c });
            }
            if (virtualStart) {
                leftHeap.push({ k: x.get(virtualStart) + this.getMinDist(virtualStart, sequence[t - 1]), v: Number.MAX_VALUE });
            }
            leftHeap.sort(this._positionDescendingComparer);

            // collect right changes
            var rightHeap = [];
            for (i = t; i < r; ++i) {
                c = 0;
                neighbors = dir === -1 ? this.downNodes.get(sequence[i]) : this.upNodes.get(sequence[i]);
                for (n = 0; n < neighbors.length; ++n) {
                    neighbor = neighbors[n];
                    if (x.get(neighbor) <= x.get(sequence[i])) {
                        c++;
                    }
                    else {
                        c--;
                        rightHeap.push({ k: x.get(neighbor) - this.getMinDist(sequence[i], sequence[t]), v: 2 });
                    }
                }
                rightHeap.push({ k: x.get(sequence[i]) - this.getMinDist(sequence[i], sequence[t]), v: c });
            }
            if (virtualEnd) {
                rightHeap.push({ k: x.get(virtualEnd) - this.getMinDist(virtualEnd, sequence[t]), v: Number.MAX_VALUE });
            }
            rightHeap.sort(this._positionAscendingComparer);

            var leftRes = 0, rightRes = 0;
            var m = this.getMinDist(sequence[t - 1], sequence[t]);
            while (x.get(sequence[t]) - x.get(sequence[t - 1]) < m) {
                if (leftRes < rightRes) {
                    if (leftHeap.length === 0) {
                        x.set(sequence[t - 1], x.get(sequence[t]) - m);
                        break;
                    }
                    else {
                        pair = leftHeap.shift();
                        leftRes = leftRes + pair.v;
                        x.set(sequence[t - 1], pair.k);
                        x.set(sequence[t - 1], Math.max(x.get(sequence[t - 1]), x.get(sequence[t]) - m));
                    }
                }
                else {
                    if (rightHeap.length === 0) {
                        x.set(sequence[t], x.get(sequence[t - 1]) + m);
                        break;
                    }
                    else {
                        pair = rightHeap.shift();
                        rightRes = rightRes + pair.v;
                        x.set(sequence[t], pair.k);
                        x.set(sequence[t], Math.min(x.get(sequence[t]), x.get(sequence[t - 1]) + m));
                    }
                }
            }
            for (i = t - 2; i >= 0; i--) {
                x.set(sequence[i], Math.min(x.get(sequence[i]), x.get(sequence[t - 1]) - this.getMinDist(sequence[i], sequence[t - 1])));
            }
            for (i = t + 1; i < r; i++) {
                x.set(sequence[i], Math.max(x.get(sequence[i]), x.get(sequence[t]) + this.getMinDist(sequence[i], sequence[t])));
            }
        },

        placeLeft: function (node, leftPos, leftClass) {
            var pos = Number.NEGATIVE_INFINITY;
            Utils.forEach(this._getComposite(node), function (v) {
                var leftSibling = this.leftSibling(v);
                if (leftSibling && this.nodeLeftClass.get(leftSibling) === this.nodeLeftClass.get(v)) {
                    if (!leftPos.containsKey(leftSibling)) {
                        this.placeLeft(leftSibling, leftPos, leftClass);
                    }
                    pos = Math.max(pos, leftPos.get(leftSibling) + this.getMinDist(leftSibling, v));
                }
            }, this);
            if (pos === Number.NEGATIVE_INFINITY) {
                pos = 0;
            }
            Utils.forEach(this._getComposite(node), function (v) {
                leftPos.set(v, pos);
            });
        },

        placeRight: function (node, rightPos, rightClass) {
            var pos = Number.POSITIVE_INFINITY;
            Utils.forEach(this._getComposite(node), function (v) {
                var rightSibling = this.rightSibling(v);
                if (rightSibling && this.nodeRightClass.get(rightSibling) === this.nodeRightClass.get(v)) {
                    if (!rightPos.containsKey(rightSibling)) {
                        this.placeRight(rightSibling, rightPos, rightClass);
                    }
                    pos = Math.min(pos, rightPos.get(rightSibling) - this.getMinDist(v, rightSibling));
                }
            }, this);
            if (pos === Number.POSITIVE_INFINITY) {
                pos = 0;
            }
            Utils.forEach(this._getComposite(node), function (v) {
                rightPos.set(v, pos);
            });
        },

        leftSibling: function (node) {
            var layer = this.layers[node.layer],
                layerIndex = node.layerIndex;
            return layerIndex === 0 ? null : layer[layerIndex - 1];
        },

        rightSibling: function (node) {
            var layer = this.layers[node.layer];
            var layerIndex = node.layerIndex;
            return layerIndex === layer.length - 1 ? null : layer[layerIndex + 1];

        },

        _getComposite: function (node) {
            return node.isVirtual ? this._nodesInLink(node) : [node];
        },

        arrangeNodes: function () {
            var i, l, ni, layer, node;
            // Initialize node's base priority
            for (l = 0; l < this.layers.length; l++) {
                layer = this.layers[l];

                for (ni = 0; ni < layer.length; ni++) {
                    node = layer[ni];
                    node.upstreamPriority = node.upstreamLinkCount;
                    node.downstreamPriority = node.downstreamLinkCount;
                }
            }

            // Layout is invoked after MinimizeCrossings
            // so we may assume node's barycenters are initially correct

            var maxLayoutIterations = 2;
            for (var it = 0; it < maxLayoutIterations; it++) {
                for (i = this.layers.length - 1; i >= 1; i--) {
                    this.layoutLayer(false, i);
                }

                for (i = 0; i < this.layers.length - 1; i++) {
                    this.layoutLayer(true, i);
                }
            }

            // Offset the whole structure so that there are no gridPositions < 0
            var gridPos = Number.MAX_VALUE;
            for (l = 0; l < this.layers.length; l++) {
                layer = this.layers[l];

                for (ni = 0; ni < layer.length; ni++) {
                    node = layer[ni];
                    gridPos = Math.min(gridPos, node.gridPosition);
                }
            }

            if (gridPos < 0) {
                for (l = 0; l < this.layers.length; l++) {
                    layer = this.layers[l];

                    for (ni = 0; ni < layer.length; ni++) {
                        node = layer[ni];
                        node.gridPosition = node.gridPosition - gridPos;
                    }
                }
            }
        },

        /// <summary>
        /// Layout of a single layer.
        /// </summary>
        /// <param name="layerIndex">The layer to organize.</param>
        /// <param name="movingDownwards">If set to <c>true</c> we move down in the layer stack.</param>
        /// <seealso cref="OptimizeCrossings()"/>
        layoutLayer: function (down, layer) {
            var iconsidered;
            var considered;

            if (down) {
                considered = this.layers[iconsidered = layer + 1];
            }
            else {
                considered = this.layers[iconsidered = layer - 1];
            }

            // list containing the nodes in the considered layer sorted by priority
            var sorted = [];
            for (var n = 0; n < considered.length; n++) {
                sorted.push(considered[n]);
            }
            sorted.sort(function (n1, n2) {
                var n1Priority = (n1.upstreamPriority + n1.downstreamPriority) / 2;
                var n2Priority = (n2.upstreamPriority + n2.downstreamPriority) / 2;

                if (Math.abs(n1Priority - n2Priority) < 0.0001) {
                    return 0;
                }
                if (n1Priority < n2Priority) {
                    return 1;
                }
                return -1;
            });

            // each node strives for its barycenter; high priority nodes start first
            Utils.forEach(sorted, function (node) {
                var nodeGridPos = node.gridPosition;
                var nodeBaryCenter = this.calcBaryCenter(node);
                var nodePriority = (node.upstreamPriority + node.downstreamPriority) / 2;

                if (Math.abs(nodeGridPos - nodeBaryCenter) < 0.0001) {
                    // This node is exactly at its barycenter -> perfect
                    return;
                }

                if (Math.abs(nodeGridPos - nodeBaryCenter) < 0.25 + 0.0001) {
                    // This node is close enough to the barycenter -> should work
                    return;
                }

                if (nodeGridPos < nodeBaryCenter) {
                    // Try to move the node to the right in an
                    // attempt to reach its barycenter
                    while (nodeGridPos < nodeBaryCenter) {
                        if (!this.moveRight(node, considered, nodePriority)) {
                            break;
                        }

                        nodeGridPos = node.gridPosition;
                    }
                }
                else {
                    // Try to move the node to the left in an
                    // attempt to reach its barycenter
                    while (nodeGridPos > nodeBaryCenter) {
                        if (!this.moveLeft(node, considered, nodePriority)) {
                            break;
                        }

                        nodeGridPos = node.gridPosition;
                    }
                }
            }, this);

            // after the layer has been rearranged we need to recalculate the barycenters
            // of the nodes in the surrounding layers
            if (iconsidered > 0) {
                this.calcDownData(iconsidered - 1);
            }
            if (iconsidered < this.layers.length - 1) {
                this.calcUpData(iconsidered + 1);
            }
        },

        /// <summary>
        /// Moves the node to the right and returns <c>true</c> if this was possible.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="layer">The layer.</param>
        /// <returns>Returns <c>true</c> if the shift was possible, otherwise <c>false</c>.</returns>
        moveRight: function (node, layer, priority) {
            var index = Utils.indexOf(layer, node);
            if (index === layer.length - 1) {
                // this is the last node in the layer, so we can move to the right without troubles
                node.gridPosition = node.gridPosition + 0.5;
                return true;
            }

            var rightNode = layer[index + 1];
            var rightNodePriority = (rightNode.upstreamPriority + rightNode.downstreamPriority) / 2;

            // check if there is space between the right and the current node
            if (rightNode.gridPosition > node.gridPosition + 1) {
                node.gridPosition = node.gridPosition + 0.5;
                return true;
            }

            // we have reached a node with higher priority; no movement is allowed
            if (rightNodePriority > priority ||
                Math.abs(rightNodePriority - priority) < 0.0001) {
                return false;
            }

            // the right node has lower priority - try to move it
            if (this.moveRight(rightNode, layer, priority)) {
                node.gridPosition = node.gridPosition + 0.5;
                return true;
            }

            return false;
        },

        /// <summary>
        /// Moves the node to the left and returns <c>true</c> if this was possible.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="layer">The layer.</param>
        /// <returns>Returns <c>true</c> if the shift was possible, otherwise <c>false</c>.</returns>
        moveLeft: function (node, layer, priority) {
            var index = Utils.indexOf(layer, node);
            if (index === 0) {
                // this is the last node in the layer, so we can move to the left without troubles
                node.gridPosition = node.gridPosition - 0.5;
                return true;
            }

            var leftNode = layer[index - 1];
            var leftNodePriority = (leftNode.upstreamPriority + leftNode.downstreamPriority) / 2;

            // check if there is space between the left and the current node
            if (leftNode.gridPosition < node.gridPosition - 1) {
                node.gridPosition = node.gridPosition - 0.5;
                return true;
            }

            // we have reached a node with higher priority; no movement is allowed
            if (leftNodePriority > priority ||
                Math.abs(leftNodePriority - priority) < 0.0001) {
                return false;
            }

            // The left node has lower priority - try to move it
            if (this.moveLeft(leftNode, layer, priority)) {
                node.gridPosition = node.gridPosition - 0.5;
                return true;
            }

            return false;
        },

        mapVirtualNode: function (node, link) {
            this.nodeToLinkMap.set(node, link);
            if (!this.linkToNodeMap.containsKey(link)) {
                this.linkToNodeMap.set(link, []);
            }
            this.linkToNodeMap.get(link).push(node);
        },

        _nodesInLink: function (node) {
            return this.linkToNodeMap.get(this.nodeToLinkMap.get(node));
        },

        /// <summary>
        /// Inserts dummy nodes to break long links.
        /// </summary>
        _dummify: function () {
            this.linkToNodeMap = new Dictionary();
            this.nodeToLinkMap = new Dictionary();

            var layer, pos, newNode, node, r, newLink, i, l, links = this.graph.links.slice(0);
            for (l = 0; l < links.length; l++) {
                var link = links[l];
                var o = link.source;
                var d = link.target;

                var oLayer = o.layer;
                var dLayer = d.layer;
                var oPos = o.gridPosition;
                var dPos = d.gridPosition;

                var step = (dPos - oPos) / Math.abs(dLayer - oLayer);

                var p = o;
                if (oLayer - dLayer > 1) {
                    for (i = oLayer - 1; i > dLayer; i--) {
                        newNode = new Node();
                        newNode.x = o.x;
                        newNode.y = o.y;
                        newNode.width = o.width / 100;
                        newNode.height = o.height / 100;

                        layer = this.layers[i];
                        pos = (i - dLayer) * step + oPos;
                        if (pos > layer.length) {
                            pos = layer.length;
                        }

                        // check if origin and dest are both last
                        if (oPos >= this.layers[oLayer].length - 1 &&
                            dPos >= this.layers[dLayer].length - 1) {
                            pos = layer.length;
                        }

                        // check if origin and destination are both first
                        else if (oPos === 0 && dPos === 0) {
                            pos = 0;
                        }

                        newNode.layer = i;
                        newNode.uBaryCenter = 0.0;
                        newNode.dBaryCenter = 0.0;
                        newNode.upstreamLinkCount = 0;
                        newNode.downstreamLinkCount = 0;
                        newNode.gridPosition = pos;
                        newNode.isVirtual = true;

                        Utils.insert(layer, newNode, pos);

                        // translate rightwards nodes' positions
                        for (r = pos + 1; r < layer.length; r++) {
                            node = layer[r];
                            node.gridPosition = node.gridPosition + 1;
                        }

                        newLink = new Link(p, newNode);
                        newLink.depthOfDumminess = 0;
                        p = newNode;

                        // add the new node and the new link to the graph
                        this.graph.nodes.push(newNode);
                        this.graph.addLink(newLink);

                        newNode.index = this.graph.nodes.length - 1;
                        this.mapVirtualNode(newNode, link);
                    }

                    // set the origin of the real arrow to the last dummy
                    link.changeSource(p);
                    link.depthOfDumminess = oLayer - dLayer - 1;
                }

                if (oLayer - dLayer < -1) {
                    for (i = oLayer + 1; i < dLayer; i++) {
                        newNode = new Node();
                        newNode.x = o.x;
                        newNode.y = o.y;
                        newNode.width = o.width / 100;
                        newNode.height = o.height / 100;

                        layer = this.layers[i];
                        pos = (i - oLayer) * step + oPos;
                        if (pos > layer.length) {
                            pos = layer.length;
                        }

                        // check if origin and dest are both last
                        if (oPos >= this.layers[oLayer].length - 1 &&
                            dPos >= this.layers[dLayer].length - 1) {
                            pos = layer.length;
                        }

                        // check if origin and destination are both first
                        else if (oPos === 0 && dPos === 0) {
                            pos = 0;
                        }

                        newNode.layer = i;
                        newNode.uBaryCenter = 0.0;
                        newNode.dBaryCenter = 0.0;
                        newNode.upstreamLinkCount = 0;
                        newNode.downstreamLinkCount = 0;
                        newNode.gridPosition = pos;
                        newNode.isVirtual = true;

                        pos &= pos; // truncates to int
                        Utils.insert(layer, newNode, pos);

                        // translate rightwards nodes' positions
                        for (r = pos + 1; r < layer.length; r++) {
                            node = layer[r];
                            node.gridPosition = node.gridPosition + 1;
                        }

                        newLink = new Link(p, newNode);
                        newLink.depthOfDumminess = 0;
                        p = newNode;

                        // add the new node and the new link to the graph
                        this.graph.nodes.push(newNode);
                        this.graph.addLink(newLink);

                        newNode.index = this.graph.nodes.length - 1;
                        this.mapVirtualNode(newNode, link);
                    }

                    // Set the origin of the real arrow to the last dummy
                    link.changeSource(p);
                    link.depthOfDumminess = dLayer - oLayer - 1;
                }
            }
        },

        /// <summary>
        /// Removes the dummy nodes inserted earlier to break long links.
        /// </summary>
        /// <remarks>The virtual nodes are effectively turned into intermediate connection points.</remarks>
        _dedummify: function () {
            var dedum = true;
            while (dedum) {
                dedum = false;

                for (var l = 0; l < this.graph.links.length; l++) {
                    var link = this.graph.links[l];
                    if (link.depthOfDumminess === 0) {
                        continue;
                    }

                    var points = [];

                    // add points in reverse order
                    points.unshift({ x: link.target.x, y: link.target.y });
                    points.unshift({ x: link.source.x, y: link.source.y });

                    // _dedummify the link
                    var temp = link;
                    var depthOfDumminess = link.depthOfDumminess;
                    for (var d = 0; d < depthOfDumminess; d++) {
                        var node = temp.source;
                        var prevLink = node.incoming[0];

                        points.unshift({ x: prevLink.source.x, y: prevLink.source.y });

                        temp = prevLink;
                    }

                    // restore the original link origin
                    link.changeSource(temp.source);

                    // reset dummification flag
                    link.depthOfDumminess = 0;

                    // note that we only need the intermediate points, floating links have been dropped in the analysis
                    if (points.length > 2) {
                        // first and last are the endpoints
                        points.splice(0, 1);
                        points.splice(points.length - 1);
                        link.points = points;
                    }
                    else {
                        link.points = [];
                    }

                    // we are not going to delete the dummy elements;
                    // they won't be needed anymore anyway.

                    dedum = true;
                    break;
                }
            }
        },

        /// <summary>
        /// Optimizes/reduces the crossings between the layers by turning the crossing problem into a (combinatorial) number ordering problem.
        /// </summary>
        _optimizeCrossings: function () {
            var moves = -1, i;
            var maxIterations = 3;
            var iter = 0;

            while (moves !== 0) {
                if (iter++ > maxIterations) {
                    break;
                }

                moves = 0;

                for (i = this.layers.length - 1; i >= 1; i--) {
                    moves += this.optimizeLayerCrossings(false, i);
                }

                for (i = 0; i < this.layers.length - 1; i++) {
                    moves += this.optimizeLayerCrossings(true, i);
                }
            }
        },

        calcUpData: function (layer) {
            if (layer === 0) {
                return;
            }

            var considered = this.layers[layer], i, l, link;
            var upLayer = new Set();
            var temp = this.layers[layer - 1];
            for (i = 0; i < temp.length; i++) {
                upLayer.add(temp[i]);
            }

            for (i = 0; i < considered.length; i++) {
                var node = considered[i];

                // calculate barycenter
                var sum = 0;
                var total = 0;

                for (l = 0; l < node.incoming.length; l++) {
                    link = node.incoming[l];
                    if (upLayer.contains(link.source)) {
                        total++;
                        sum += link.source.gridPosition;
                    }
                }

                for (l = 0; l < node.outgoing.length; l++) {
                    link = node.outgoing[l];
                    if (upLayer.contains(link.target)) {
                        total++;
                        sum += link.target.gridPosition;
                    }
                }

                if (total > 0) {
                    node.uBaryCenter = sum / total;
                    node.upstreamLinkCount = total;
                }
                else {
                    node.uBaryCenter = i;
                    node.upstreamLinkCount = 0;
                }
            }
        },

        calcDownData: function (layer) {
            if (layer === this.layers.length - 1) {
                return;
            }

            var considered = this.layers[layer], i, l, link;
            var downLayer = new Set();
            var temp = this.layers[layer + 1];
            for (i = 0; i < temp.length; i++) {
                downLayer.add(temp[i]);
            }

            for (i = 0; i < considered.length; i++) {
                var node = considered[i];

                // calculate barycenter
                var sum = 0;
                var total = 0;

                for (l = 0; l < node.incoming.length; l++) {
                    link = node.incoming[l];
                    if (downLayer.contains(link.source)) {
                        total++;
                        sum += link.source.gridPosition;
                    }
                }

                for (l = 0; l < node.outgoing.length; l++) {
                    link = node.outgoing[l];
                    if (downLayer.contains(link.target)) {
                        total++;
                        sum += link.target.gridPosition;
                    }
                }

                if (total > 0) {
                    node.dBaryCenter = sum / total;
                    node.downstreamLinkCount = total;
                }
                else {
                    node.dBaryCenter = i;
                    node.downstreamLinkCount = 0;
                }
            }
        },

        /// <summary>
        /// Optimizes the crossings.
        /// </summary>
        /// <remarks>The big trick here is the usage of weights or values attached to connected nodes which turn a problem of crossing links
        /// to an a problem of ordering numbers.</remarks>
        /// <param name="layerIndex">The layer index.</param>
        /// <param name="movingDownwards">If set to <c>true</c> we move down in the layer stack.</param>
        /// <returns>The number of nodes having moved, i.e. the number of crossings reduced.</returns>
        optimizeLayerCrossings: function (down, layer) {
            var iconsidered;
            var considered;

            if (down) {
                considered = this.layers[iconsidered = layer + 1];
            }
            else {
                considered = this.layers[iconsidered = layer - 1];
            }

            // remember what it was
            var presorted = considered.slice(0);

            // calculate barycenters for all nodes in the considered layer
            if (down) {
                this.calcUpData(iconsidered);
            }
            else {
                this.calcDownData(iconsidered);
            }

            var that = this;
            // sort nodes within this layer according to the barycenters
            considered.sort(function (n1, n2) {
                var n1BaryCenter = that.calcBaryCenter(n1),
                    n2BaryCenter = that.calcBaryCenter(n2);
                if (Math.abs(n1BaryCenter - n2BaryCenter) < 0.0001) {
                    // in case of coinciding barycenters compare by the count of in/out links
                    if (n1.degree() === n2.degree()) {
                        return that.compareByIndex(n1, n2);
                    }
                    else if (n1.degree() < n2.degree()) {
                        return 1;
                    }
                    return -1;
                }
                var compareValue = (n2BaryCenter - n1BaryCenter) * 1000;
                if (compareValue > 0) {
                    return -1;
                }
                else if (compareValue < 0) {
                    return 1;
                }
                return that.compareByIndex(n1, n2);
            });

            // count relocations
            var i, moves = 0;
            for (i = 0; i < considered.length; i++) {
                if (considered[i] !== presorted[i]) {
                    moves++;
                }
            }

            if (moves > 0) {
                // now that the boxes have been arranged, update their grid positions
                var inode = 0;
                for (i = 0; i < considered.length; i++) {
                    var node = considered[i];
                    node.gridPosition = inode++;
                }
            }

            return moves;
        },

        /// <summary>
        /// Swaps a pair of nodes in a layer.
        /// </summary>
        /// <param name="layerIndex">Index of the layer.</param>
        /// <param name="n">The Nth node in the layer.</param>
        _swapPairs: function () {
            var maxIterations = this.options.layeredIterations;
            var iter = 0;

            while (true) {
                if (iter++ > maxIterations) {
                    break;
                }

                var downwards = (iter % 4 <= 1);
                var secondPass = (iter % 4 === 1);

                for (var l = (downwards ? 0 : this.layers.length - 1);
                     downwards ? l <= this.layers.length - 1 : l >= 0; l += (downwards ? 1 : -1)) {
                    var layer = this.layers[l];
                    var hasSwapped = false;

                    // there is no need to recalculate crossings if they were calculated
                    // on the previous step and nothing has changed
                    var calcCrossings = true;
                    var memCrossings = 0;

                    for (var n = 0; n < layer.length - 1; n++) {
                        // count crossings
                        var up = 0;
                        var down = 0;
                        var crossBefore = 0;

                        if (calcCrossings) {
                            if (l !== 0) {
                                up = this.countLinksCrossingBetweenTwoLayers(l - 1, l);
                            }
                            if (l !== this.layers.length - 1) {
                                down = this.countLinksCrossingBetweenTwoLayers(l, l + 1);
                            }
                            if (downwards) {
                                up *= 2;
                            }
                            else {
                                down *= 2;
                            }

                            crossBefore = up + down;
                        }
                        else {
                            crossBefore = memCrossings;
                        }

                        if (crossBefore === 0) {
                            continue;
                        }

                        // Swap nodes
                        var node1 = layer[n];
                        var node2 = layer[n + 1];

                        var node1GridPos = node1.gridPosition;
                        var node2GridPos = node2.gridPosition;
                        layer[n] = node2;
                        layer[n + 1] = node1;
                        node1.gridPosition = node2GridPos;
                        node2.gridPosition = node1GridPos;

                        // count crossings again and if worse than before, restore swapping
                        up = 0;
                        if (l !== 0) {
                            up = this.countLinksCrossingBetweenTwoLayers(l - 1, l);
                        }
                        down = 0;
                        if (l !== this.layers.length - 1) {
                            down = this.countLinksCrossingBetweenTwoLayers(l, l + 1);
                        }
                        if (downwards) {
                            up *= 2;
                        }
                        else {
                            down *= 2;
                        }
                        var crossAfter = up + down;

                        var revert = false;
                        if (secondPass) {
                            revert = crossAfter >= crossBefore;
                        }
                        else {
                            revert = crossAfter > crossBefore;
                        }

                        if (revert) {
                            node1 = layer[n];
                            node2 = layer[n + 1];

                            node1GridPos = node1.gridPosition;
                            node2GridPos = node2.gridPosition;
                            layer[n] = node2;
                            layer[n + 1] = node1;
                            node1.gridPosition = node2GridPos;
                            node2.gridPosition = node1GridPos;

                            // nothing has changed, remember the crossings so that
                            // they are not calculated again on the next step
                            memCrossings = crossBefore;
                            calcCrossings = false;
                        }
                        else {
                            hasSwapped = true;
                            calcCrossings = true;
                        }
                    }

                    if (hasSwapped) {
                        if (l !== this.layers.length - 1) {
                            this.calcUpData(l + 1);
                        }
                        if (l !== 0) {
                            this.calcDownData(l - 1);
                        }
                    }
                }
            }
        },

        /// <summary>
        /// Counts the number of links crossing between two layers.
        /// </summary>
        /// <param name="layerIndex1">The layer index.</param>
        /// <param name="layerIndex2">Another layer index.</param>
        /// <returns></returns>
        countLinksCrossingBetweenTwoLayers: function (ulayer, dlayer) {
            var i, crossings = 0;

            var upperLayer = new Set();
            var temp1 = this.layers[ulayer];
            for (i = 0; i < temp1.length; i++) {
                upperLayer.add(temp1[i]);
            }

            var lowerLayer = new Set();
            var temp2 = this.layers[dlayer];
            for (i = 0; i < temp2.length; i++) {
                lowerLayer.add(temp2[i]);
            }

            // collect the links located between the layers
            var dlinks = new Set();
            var links = [];
            var temp = [];

            upperLayer.forEach(function (node) {
                //throw "";
                Utils.addRange(temp, node.incoming);
                Utils.addRange(temp, node.outgoing);
            });

            for (var ti = 0; ti < temp.length; ti++) {
                var link = temp[ti];

                if (upperLayer.contains(link.source) &&
                    lowerLayer.contains(link.target)) {
                    dlinks.add(link);
                    links.push(link);
                }
                else if (lowerLayer.contains(link.source) &&
                    upperLayer.contains(link.target)) {
                    links.push(link);
                }
            }

            for (var l1 = 0; l1 < links.length; l1++) {
                var link1 = links[l1];
                for (var l2 = 0; l2 < links.length; l2++) {
                    if (l1 === l2) {
                        continue;
                    }

                    var link2 = links[l2];

                    var n11, n12;
                    var n21, n22;

                    if (dlinks.contains(link1)) {
                        n11 = link1.source;
                        n12 = link1.target;
                    }
                    else {
                        n11 = link1.target;
                        n12 = link1.source;
                    }

                    if (dlinks.contains(link2)) {
                        n21 = link2.source;
                        n22 = link2.target;
                    }
                    else {
                        n21 = link2.target;
                        n22 = link2.source;
                    }

                    var n11gp = n11.gridPosition;
                    var n12gp = n12.gridPosition;
                    var n21gp = n21.gridPosition;
                    var n22gp = n22.gridPosition;

                    if ((n11gp - n21gp) * (n12gp - n22gp) < 0) {
                        crossings++;
                    }
                }
            }

            return crossings / 2;
        },

        calcBaryCenter: function (node) {
            var upstreamLinkCount = node.upstreamLinkCount;
            var downstreamLinkCount = node.downstreamLinkCount;
            var uBaryCenter = node.uBaryCenter;
            var dBaryCenter = node.dBaryCenter;

            if (upstreamLinkCount > 0 && downstreamLinkCount > 0) {
                return (uBaryCenter + dBaryCenter) / 2;
            }
            if (upstreamLinkCount > 0) {
                return uBaryCenter;
            }
            if (downstreamLinkCount > 0) {
                return dBaryCenter;
            }

            return 0;
        },

        _gridPositionComparer: function (x, y) {
            if (x.gridPosition < y.gridPosition) {
                return -1;
            }
            if (x.gridPosition > y.gridPosition) {
                return 1;
            }
            return 0;
        },

        _positionAscendingComparer: function (x, y) {
            return x.k < y.k ? -1 : x.k > y.k ? 1 : 0;
        },

        _positionDescendingComparer: function (x, y) {
            return x.k < y.k ? 1 : x.k > y.k ? -1 : 0;
        },

        _firstVirtualNode: function (layer) {
            for (var c = 0; c < layer.length; c++) {
                if (layer[c].isVirtual) {
                    return c;
                }
            }
            return -1;
        },

        compareByIndex: function (o1, o2) {
            var i1 = o1.index;
            var i2 = o2.index;

            if (i1 < i2) {
                return 1;
            }

            if (i1 > i2) {
                return -1;
            }

            return 0;
        },

        intDiv: function (numerator, denominator) {
            return (numerator - numerator % denominator) / denominator;
        },

        nextVirtualNode: function (layer, node) {
            var nodeIndex = node.layerIndex;
            for (var i = nodeIndex + 1; i < layer.length; ++i) {
                if (layer[i].isVirtual) {
                    return layer[i];
                }
            }
            return null;
        }

    });

    /**
    * Captures the state of a diagram; node positions, link points and so on.
    * @type {*}
    */
    var LayoutState = kendo.Class.extend({
        init: function (diagram, graphOrNodes) {
            if (Utils.isUndefined(diagram)) {
                throw "No diagram given";
            }
            this.diagram = diagram;
            this.nodeMap = new Dictionary();
            this.linkMap = new Dictionary();
            this.capture(graphOrNodes ? graphOrNodes : diagram);
        },

        /**
        * Will capture either
        * - the state of the shapes and the intermediate points of the connections in the diagram
        * - the bounds of the nodes contained in the Graph together with the intermediate points of the links in the Graph
        * - the bounds of the nodes in the Array<Node>
        * - the links points and node bounds in the literal object
        * @param diagramOrGraphOrNodes
        */
        capture: function (diagramOrGraphOrNodes) {
            var node,
                nodes,
                shape,
                i,
                conn,
                link,
                links;

            if (diagramOrGraphOrNodes instanceof diagram.Graph) {

                for (i = 0; i < diagramOrGraphOrNodes.nodes.length; i++) {
                    node = diagramOrGraphOrNodes.nodes[i];
                    shape = node.associatedShape;
                    //shape.bounds(new Rect(node.x, node.y, node.width, node.height));
                    this.nodeMap.set(shape.visual.id, new Rect(node.x, node.y, node.width, node.height));
                }
                for (i = 0; i < diagramOrGraphOrNodes.links.length; i++) {
                    link = diagramOrGraphOrNodes.links[i];
                    conn = link.associatedConnection;
                    this.linkMap.set(conn.visual.id, link.points());
                }
            }
            else if (diagramOrGraphOrNodes instanceof Array) {
                nodes = diagramOrGraphOrNodes;
                for (i = 0; i < nodes.length; i++) {
                    node = nodes[i];
                    shape = node.associatedShape;
                    if (shape) {
                        this.nodeMap.set(shape.visual.id, new Rect(node.x, node.y, node.width, node.height));
                    }
                }
            }
            else if (diagramOrGraphOrNodes.hasOwnProperty("links") && diagramOrGraphOrNodes.hasOwnProperty("nodes")) {
                nodes = diagramOrGraphOrNodes.nodes;
                links = diagramOrGraphOrNodes.links;
                for (i = 0; i < nodes.length; i++) {
                    node = nodes[i];
                    shape = node.associatedShape;
                    if (shape) {
                        this.nodeMap.set(shape.visual.id, new Rect(node.x, node.y, node.width, node.height));
                    }
                }
                for (i = 0; i < links.length; i++) {
                    link = links[i];
                    conn = link.associatedConnection;
                    if (conn) {
                        this.linkMap.set(conn.visual.id, link.points);
                    }
                }
            }
            else { // capture the diagram
                var shapes = this.diagram.shapes;
                var connections = this.diagram.connections;
                for (i = 0; i < shapes.length; i++) {
                    shape = shapes[i];
                    this.nodeMap.set(shape.visual.id, shape.bounds());
                }
                for (i = 0; i < connections.length; i++) {
                    conn = connections[i];
                    this.linkMap.set(conn.visual.id, conn.points());
                }
            }
        }
    });

    deepExtend(diagram, {
        init: function (element) {
            kendo.init(element, diagram.ui);
        },
        SpringLayout: SpringLayout,
        TreeLayout: TreeLayout,
        GraphAdapter: DiagramToHyperTreeAdapter,
        LayeredLayout: LayeredLayout,
        LayoutBase: LayoutBase,
        LayoutState: LayoutState
    });
})(window.kendo.jQuery);

(function ($, undefined) {
    // Imports ================================================================
    var dataviz = kendo.dataviz,
            draw = kendo.drawing,
            geom = kendo.geometry,
            diagram = dataviz.diagram,
            Widget = kendo.ui.Widget,
            Class = kendo.Class,
            proxy = $.proxy,
            deepExtend = kendo.deepExtend,
            extend = $.extend,
            HierarchicalDataSource = kendo.data.HierarchicalDataSource,
            Canvas = diagram.Canvas,
            Group = diagram.Group,
            Visual = diagram.Visual,
            Rectangle = diagram.Rectangle,
            Circle = diagram.Circle,
            CompositeTransform = diagram.CompositeTransform,
            Rect = diagram.Rect,
            Path = diagram.Path,
            DeleteShapeUnit = diagram.DeleteShapeUnit,
            DeleteConnectionUnit = diagram.DeleteConnectionUnit,
            TextBlock = diagram.TextBlock,
            Image = diagram.Image,
            Point = diagram.Point,
            Intersect = diagram.Intersect,
            ConnectionEditAdorner = diagram.ConnectionEditAdorner,
            UndoRedoService = diagram.UndoRedoService,
            ToolService = diagram.ToolService,
            Selector = diagram.Selector,
            ResizingAdorner = diagram.ResizingAdorner,
            ConnectorsAdorner = diagram.ConnectorsAdorner,
            Cursors = diagram.Cursors,
            Utils = diagram.Utils,
            Observable = kendo.Observable,
            Ticker = diagram.Ticker,
            ToBackUnit = diagram.ToBackUnit,
            ToFrontUnit = diagram.ToFrontUnit,
            Dictionary = diagram.Dictionary,
            PolylineRouter = diagram.PolylineRouter,
            CascadingRouter = diagram.CascadingRouter,
            isUndefined = Utils.isUndefined,
            isDefined = Utils.isDefined,
            defined = kendo.util.defined,
            isArray = $.isArray,
            isFunction = kendo.isFunction,
            isString = Utils.isString,
            isPlainObject = $.isPlainObject,

            math = Math;

    // Constants ==============================================================
    var NS = ".kendoDiagram",
            CASCADING = "cascading",
            POLYLINE = "polyline",
            ITEMBOUNDSCHANGE = "itemBoundsChange",
            CHANGE = "change",
            CLICK = "click",
            ERROR = "error",
            AUTO = "Auto",
            TOP = "Top",
            RIGHT = "Right",
            LEFT = "Left",
            BOTTOM = "Bottom",
            MAXINT = 9007199254740992,
            SELECT = "select",
            ITEMROTATE = "itemRotate",
            PAN = "pan",
            ZOOM_START = "zoomStart",
            ZOOM_END = "zoomEnd",
            CONNECTION_CSS = "k-connection",
            SHAPE_CSS = "k-shape",
            SINGLE = "single",
            NONE = "none",
            MULTIPLE = "multiple",
            DEFAULT_CANVAS_WIDTH = 600,
            DEFAULT_CANVAS_HEIGHT = 600,
            DEFAULT_SHAPE_TYPE = "rectangle",
            DEFAULT_SHAPE_WIDTH = 100,
            DEFAULT_SHAPE_HEIGHT = 100,
            DEFAULT_SHAPE_MINWIDTH = 20,
            DEFAULT_SHAPE_MINHEIGHT = 20,
            DEFAULT_SHAPE_POSITION = 0,
            DEFAULT_SHAPE_BACKGROUND = "SteelBlue",
            DEFAULT_CONNECTION_BACKGROUND = "Yellow",
            DEFAULT_CONNECTOR_SIZE = 8,
            DEFAULT_HOVER_COLOR = "#70CAFF",
            MAX_VALUE = Number.MAX_VALUE,
            MIN_VALUE = -Number.MAX_VALUE,
            ALL = "all",
            ABSOLUTE = "absolute",
            TRANSFORMED = "transformed",
            ROTATED = "rotated",
            TRANSPARENT = "transparent",
            WIDTH = "width",
            HEIGHT = "height",
            X = "x",
            Y = "y",
            MOUSEWHEEL_NS = "DOMMouseScroll" + NS + " mousewheel" + NS,
            MOBILE_ZOOM_RATE = 0.05,
            MOBILE_PAN_DISTANCE = 5,
            BUTTON_TEMPLATE = '<a class="k-button k-button-icontext #=className#" href="\\#"><span class="#=iconClass# #=imageClass#"></span>#=text#</a>';

    diagram.DefaultConnectors = [{
        name: TOP,
        description: "Top Connector"
    }, {
        name: RIGHT,
        description: "Right Connector"
    }, {
        name: BOTTOM,
        description: "Bottom Connector"
    }, {
        name: LEFT,
        Description: "Left Connector"
    }, {
        name: AUTO,
        Description: "Auto Connector",
        position: function (shape) {
            return shape.getPosition("center");
        }
    }];

    var defaultButtons = {
        cancel: {
            text: "Cancel",
            imageClass: "k-cancel",
            className: "k-diagram-cancel",
            iconClass: "k-icon"
        },
        update: {
            text: "Update",
            imageClass: "k-update",
            className: "k-diagram-update",
            iconClass: "k-icon"
        }
    };

    diagram.shapeDefaults = function (extra) {
        var defaults = {
            type: DEFAULT_SHAPE_TYPE,
            path: "",
            autoSize: true,
            visual: null,
            x: DEFAULT_SHAPE_POSITION,
            y: DEFAULT_SHAPE_POSITION,
            minWidth: DEFAULT_SHAPE_MINWIDTH,
            minHeight: DEFAULT_SHAPE_MINHEIGHT,
            width: DEFAULT_SHAPE_WIDTH,
            height: DEFAULT_SHAPE_HEIGHT,
            hover: {},
            editable: {
                connect: true,
                tools: []
            },
            connectors: diagram.DefaultConnectors,
            rotation: {
                angle: 0
            }
        };

        Utils.simpleExtend(defaults, extra);

        return defaults;
    };

    function mwDelta(e) {
        var origEvent = e.originalEvent,
                delta = 0;

        if (origEvent.wheelDelta) {
            delta = -origEvent.wheelDelta / 40;
            delta = delta > 0 ? math.ceil(delta) : math.floor(delta);
        } else if (origEvent.detail) {
            delta = origEvent.detail;
        }

        return delta;
    }

    function isAutoConnector(connector) {
        return connector.options.name.toLowerCase() === AUTO.toLowerCase();
    }

    function resolveConnectors(connection) {
        var minDist = MAXINT,
                sourcePoint, targetPoint,
                source = connection.source(),
                target = connection.target(),
                autoSourceShape,
                autoTargetShape,
                sourceConnector,
                preferred = [0, 2, 3, 1, 4],
                k;
        if (source instanceof Point) {
            sourcePoint = source;
        } else if (source instanceof Connector) {
            if (isAutoConnector(source)) {
                autoSourceShape = source.shape;
            } else {
                connection._resolvedSourceConnector = source;
                sourcePoint = source.position();
            }
        }

        if (target instanceof Point) {
            targetPoint = target;
        } else if (target instanceof Connector) {
            if (isAutoConnector(target)) {
                autoTargetShape = target.shape;
            } else {
                connection._resolvedTargetConnector = target;
                targetPoint = target.position();
            }
        }

        if (sourcePoint) {
            if (autoTargetShape) {
                connection._resolvedTargetConnector = closestConnector(sourcePoint, autoTargetShape);
            }
        } else if (autoSourceShape) {
            if (targetPoint) {
                connection._resolvedSourceConnector = closestConnector(targetPoint, autoSourceShape);
            } else if (autoTargetShape) {
                for (var i = 0; i < autoSourceShape.connectors.length; i++) {
                    if (autoSourceShape.connectors.length == 5) // presuming this means the default connectors
                    {
                        // will emphasize the vertical or horizontal direction, which matters when using the cascading router and distances which are equal for multiple connectors.
                        k = preferred[i];
                    } else {
                        k = i;
                    }
                    sourceConnector = autoSourceShape.connectors[k];
                    if (!isAutoConnector(sourceConnector)) {
                        var currentSourcePoint = sourceConnector.position(),
                                currentTargetConnector = closestConnector(currentSourcePoint, autoTargetShape);
                        var dist = math.round(currentTargetConnector.position().distanceTo(currentSourcePoint)); // rounding prevents some not needed connectors switching.
                        if (dist < minDist) {
                            minDist = dist;
                            connection._resolvedSourceConnector = sourceConnector;
                            connection._resolvedTargetConnector = currentTargetConnector;
                        }
                    }
                }
            }
        }
    }

    function closestConnector(point, shape) {
        var minimumDistance = MAXINT, resCtr, ctrs = shape.connectors;
        for (var i = 0; i < ctrs.length; i++) {
            var ctr = ctrs[i];
            if (!isAutoConnector(ctr)) {
                var dist = point.distanceTo(ctr.position());
                if (dist < minimumDistance) {
                    minimumDistance = dist;
                    resCtr = ctr;
                }
            }
        }
        return resCtr;
    }

    function indicesOfItems(group, visuals) {
        var i, indices = [], visual;
        var children = group.drawingContainer().children;
        var length = children.length;
        for (i = 0; i < visuals.length; i++) {
            visual = visuals[i];
            for (var j = 0; j < length; j++) {
                if (children[j] == visual.drawingContainer()) {
                    indices.push(j);
                    break;
                }
            }
        }
        return indices;
    }

    function deserializeConnector(diagram, value) {
        var point = Point.parse(value), ctr;
        if (point) {
            return point;
        }
        ctr = Connector.parse(diagram, value);
        if (ctr) {
            return ctr;
        }
    }

    var DiagramElement = Observable.extend({
        init: function (options) {
            var that = this;
            that.dataItem = (options || {}).dataItem;
            Observable.fn.init.call(that);
            that.options = deepExtend({ id: diagram.randomId() }, that.options, options);
            // Update dataItem after deepExtend in order to fix editing model wrapping
            if (that.dataItem) {
                that.options.dataItem = that.dataItem;
            }
            that.isSelected = false;
            that.visual = new Group({
                id: that.options.id,
                autoSize: that.options.autoSize
            });
            that._template();
        },

        options: {
            hover: {},
            cursor: Cursors.grip,
            content: {
                align: "center middle"
            },
            selectable: true,
            serializable: true,
            enable: true
        },

        _getCursor: function (point) {
            if (this.adorner) {
                return this.adorner._getCursor(point);
            }
            return this.options.cursor;
        },

        visible: function (value) {
            if (isUndefined(value)) {
                return this.visual.visible();
            } else {
                this.visual.visible(value);
            }
        },

        bounds: function () {
        },

        refresh: function () {
            this.visual.redraw();
        },

        position: function (point) {
            this.options.x = point.x;
            this.options.y = point.y;
            this.visual.position(point);
        },

        toString: function () {
            return this.options.id;
        },

        serialize: function () {
            // the options json object describes the shape perfectly. So this object can serve as shape serialization.
            var json = deepExtend({}, { options: this.options });
            if (this.options.dataItem) {
                json.dataItem = this.options.dataItem.toString();
            }
            return json;
        },

        _content: function (content) {
            if (content !== undefined) {
                var options = this.options;
                var bounds = this.bounds();

                if (diagram.Utils.isString(content)) {
                    options.content.text = content;
                } else {
                    deepExtend(options.content, content);
                }

                var contentOptions = options.content;
                var contentVisual = this._contentVisual;

                if (!contentVisual && contentOptions.text) {
                    this._contentVisual = new TextBlock(contentOptions);
                    this._contentVisual._includeInBBox = false;
                    this.visual.append(this._contentVisual);
                } else if (contentVisual) {
                    contentVisual.redraw(contentOptions);
                }
            }

            return this.options.content.text;
        },

        _hitTest: function (point) {
            var bounds = this.bounds();
            return this.visible() && bounds.contains(point) && this.options.enable;
        },

        _template: function () {
            var that = this;
            if (that.options.content.template) {
                var data = that.options.dataItem || {},
                        elementTemplate = kendo.template(that.options.content.template, {
                            paramName: "dataItem"
                        });

                that.options.content.text = elementTemplate(data);
            }
        },

        _canSelect: function () {
            return this.options.selectable !== false;
        },

        toJSON: function () {
            return {
                id: this.options.id
            };
        }
    });

    var Connector = Class.extend({
        init: function (shape, options) {
            this.options = deepExtend({}, this.options, options);
            this.connections = [];
            this.shape = shape;
        },
        options: {
            width: 7,
            height: 7,
            fill: {
                color: DEFAULT_CONNECTION_BACKGROUND
            },
            hover: {}
        },
        position: function () {
            if (this.options.position) {
                return this.options.position(this.shape);
            } else {
                return this.shape.getPosition(this.options.name);
            }
        },
        toJSON: function () {
            return {
                shapeId: this.shape.toString(),
                connector: this.options.name
            };
        }
    });

    Connector.parse = function (diagram, str) {
        var tempStr = str.split(":"),
                id = tempStr[0],
                name = tempStr[1] || AUTO;

        for (var i = 0; i < diagram.shapes.length; i++) {
            var shape = diagram.shapes[i];
            if (shape.options.id == id) {
                return shape.getConnector(name.trim());
            }
        }
    };

    var Shape = DiagramElement.extend({
        init: function (options) {
            var that = this;
            DiagramElement.fn.init.call(that, options);
            this.updateOptionsFromModel();
            options = that.options;
            that.connectors = [];
            that.type = options.type;
            that.shapeVisual = Shape.createShapeVisual(that.options);
            that.visual.append(this.shapeVisual);
            that.updateBounds();
            that.content(that.content());

            // TODO: Swa added for phase 2; included here already because the GraphAdapter takes it into account
            that._createConnectors();
            that.parentContainer = null;
            that.isContainer = false;
            that.isCollapsed = false;
            that.id = that.visual.id;

            if (options.hasOwnProperty("layout") && options.layout !== undefined) {
                // pass the defined shape layout, it overtakes the default resizing
                that.layout = options.layout.bind(options);
            }
        },

        options: diagram.shapeDefaults(),

        updateOptionsFromModel: function (model, field) {
            if (this.diagram && this.diagram._isEditable) {
                var modelOptions = filterShapeDataItem(model || this.options.dataItem);

                if (model) {
                    if (field && !dataviz.inArray(field, ["x", "y", "width", "height"])) {
                        if (this.options.visual) {
                            this.redrawVisual();
                        } else if (modelOptions.type) {
                            this.options = deepExtend({}, this.options, modelOptions);
                            this.redrawVisual();
                        }

                        if (this.options.content) {
                            this._template();
                            this.content(this.options.content);
                        }
                    }
                } else {
                    this.options = deepExtend({}, this.options, modelOptions);
                }
            }
        },

        redrawVisual: function () {
            this.visual.clear();
            this._contentVisual = null;
            this.shapeVisual = Shape.createShapeVisual(this.options);
            this.visual.append(this.shapeVisual);
            this.updateBounds();
        },

        updateModel: function () {
            if (this.diagram && this.diagram._isEditable) {
                var bounds = this._bounds;
                var model;
                if (this.options.dataItem) {
                    model = this.diagram.dataSource.getByUid(this.options.dataItem.uid);
                }

                if (model) {
                    if (defined(model.x) && bounds.x !== model.x) {
                        model.set("x", bounds.x);
                    }

                    if (defined(model.y) && bounds.y !== model.y) {
                        model.set("y", bounds.y);
                    }

                    if (defined(model.width) && bounds.width !== model.width) {
                        model.set("width", bounds.width);
                    }

                    if (defined(model.height) && bounds.height !== model.height) {
                        model.set("height", bounds.height);
                    }

                    this.dataItem = this.options.dataItem = model;
                }
            }
        },

        updateBounds: function () {
            var bounds = this.visual._measure(true);
            var options = this.options;
            this.bounds(new Rect(options.x, options.y, bounds.width, bounds.height));
            this._rotate();
            this._alignContent();
        },

        content: function (content) {
            var result = this._content(content);

            this._alignContent();

            return result;
        },

        _alignContent: function () {
            var contentOptions = this.options.content || {};
            var contentVisual = this._contentVisual;
            if (contentVisual && contentOptions.align) {
                var containerRect = this.visual._measure();
                var aligner = new diagram.RectAlign(containerRect);
                var contentBounds = contentVisual.drawingElement.bbox(null);

                var contentRect = new Rect(0, 0, contentBounds.width(), contentBounds.height());
                var alignedBounds = aligner.align(contentRect, contentOptions.align);

                contentVisual.position(alignedBounds.topLeft());
            }
        },

        _createConnectors: function () {
            var options = this.options,
                    length = options.connectors.length,
                    connectorDefaults = options.connectorDefaults,
                    connector, i;

            for (i = 0; i < length; i++) {
                connector = new Connector(
                        this, deepExtend({},
                            connectorDefaults,
                            options.connectors[i]
                        )
                    );
                this.connectors.push(connector);
            }
        },

        bounds: function (value) {
            var bounds;

            if (value) {
                if (isString(value)) {
                    switch (value) {
                        case TRANSFORMED:
                            bounds = this._transformedBounds();
                            break;
                        case ABSOLUTE:
                            bounds = this._transformedBounds();
                            var pan = this.diagram._pan;
                            bounds.x += pan.x;
                            bounds.y += pan.y;
                            break;
                        case ROTATED:
                            bounds = this._rotatedBounds();
                            break;
                        default:
                            bounds = this._bounds;
                    }
                } else {
                    this._setBounds(value);
                    this.refreshConnections();
                    this._triggerBoundsChange();
                }
            } else {
                bounds = this._bounds;
            }

            return bounds;
        },

        _setBounds: function (rect) {
            var options = this.options;
            var topLeft = rect.topLeft();
            var x = options.x = topLeft.x;
            var y = options.y = topLeft.y;
            var width = options.width = math.max(rect.width, options.minWidth);
            var height = options.height = math.max(rect.height, options.minHeight);

            this._bounds = new Rect(x, y, width, height);

            this.visual.redraw({
                x: x,
                y: y,
                width: width,
                height: height
            });
        },

        position: function (point) {
            if (point) {
                this.bounds(new Rect(point.x, point.y, this._bounds.width, this._bounds.height));
            } else {
                return this._bounds.topLeft();
            }
        },
        /**
        * Returns a clone of this shape.
        * @returns {Shape}
        */
        clone: function () {
            var json = this.serialize();

            json.options.id = diagram.randomId();

            if (this.diagram && this.diagram._isEditable && defined(this.options.dataItem)) {
                json.options.dataItem = this.options.dataItem.toJSON();
                json.options.dataItem[this.options.dataItem.idField] = this.options.dataItem._defaultId;
            }

            return new Shape(json.options);
        },

        select: function (value) {
            var diagram = this.diagram, selected, deselected;
            if (isUndefined(value)) {
                value = true;
            }

            if (this._canSelect()) {
                if (this.isSelected != value) {
                    selected = [];
                    deselected = [];
                    this.isSelected = value;
                    if (this.isSelected) {
                        diagram._selectedItems.push(this);
                        selected.push(this);
                    } else {
                        Utils.remove(diagram._selectedItems, this);
                        deselected.push(this);
                    }

                    if (!diagram._internalSelection) {
                        diagram._selectionChanged(selected, deselected);
                    }

                    return true;
                }
            }
        },

        rotate: function (angle, center, undoable) { // we assume the center is always the center of the shape.
            var rotate = this.visual.rotate();
            if (angle !== undefined) {
                if (undoable !== false) {
                    if (this.diagram && this.diagram.undoRedoService) {
                        this.diagram.undoRedoService.add(
                                new diagram.RotateUnit(this.diagram._resizingAdorner, [this], [rotate.angle]), false);
                    }
                }

                var b = this.bounds(),
                        sc = new Point(b.width / 2, b.height / 2),
                        deltaAngle,
                        newPosition;

                if (center) {
                    deltaAngle = angle - rotate.angle;
                    newPosition = b.center().rotate(center, 360 - deltaAngle).minus(sc);
                    this._rotationOffset = this._rotationOffset.plus(newPosition.minus(b.topLeft()));
                    this.position(newPosition);
                }

                this.visual.rotate(angle, sc);
                this.options.rotation.angle = angle;

                if (this.diagram && this.diagram._connectorsAdorner) {
                    this.diagram._connectorsAdorner.refresh();
                }

                this.refreshConnections();

                if (this.diagram) {
                    this.diagram.trigger(ITEMROTATE, { item: this });
                }
            }

            return rotate;
        },

        connections: function (type) { // in, out, undefined = both
            var result = [], i, j, con, cons, ctr;

            for (i = 0; i < this.connectors.length; i++) {
                ctr = this.connectors[i];
                cons = ctr.connections;
                for (j = 0, cons; j < cons.length; j++) {
                    con = cons[j];
                    if (type == "out") {
                        var source = con.source();
                        if (source.shape && source.shape == this) {
                            result.push(con);
                        }
                    } else if (type == "in") {
                        var target = con.target();
                        if (target.shape && target.shape == this) {
                            result.push(con);
                        }
                    } else {
                        result.push(con);
                    }
                }
            }

            return result;
        },

        refreshConnections: function () {
            $.each(this.connections(), function () {
                this.refresh();
            });
        },
        /**
        * Gets a connector of this shape either by the connector's supposed name or
        * via a Point in which case the closest connector will be returned.
        * @param nameOrPoint The name of a Connector or a Point.
        * @returns {Connector}
        */
        getConnector: function (nameOrPoint) {
            var i, ctr;
            if (isString(nameOrPoint)) {
                nameOrPoint = nameOrPoint.toLocaleLowerCase();
                for (i = 0; i < this.connectors.length; i++) {
                    ctr = this.connectors[i];
                    if (ctr.options.name.toLocaleLowerCase() == nameOrPoint) {
                        return ctr;
                    }
                }
            } else if (nameOrPoint instanceof Point) {
                return closestConnector(nameOrPoint, this);
            } else {
                return this.connectors.length ? this.connectors[0] : null;
            }
        },

        getPosition: function (side) {
            var b = this.bounds(),
                    fnName = side.charAt(0).toLowerCase() + side.slice(1);

            if (isFunction(b[fnName])) {
                return this._transformPoint(b[fnName]());
            }

            return b.center();
        },

        redraw: function (options) {
            if (options) {
                var shapeOptions = this.options;
                var boundsChange;

                this.shapeVisual.redraw(this._visualOptions(options));

                if (this._diffNumericOptions(options, [WIDTH, HEIGHT, X, Y])) {
                    this.bounds(new Rect(shapeOptions.x, shapeOptions.y, shapeOptions.width, shapeOptions.height));
                    boundsChange = true;
                }

                shapeOptions = deepExtend(shapeOptions, options);

                if (options.rotation || boundsChange) {
                    this._rotate();
                }

                if (shapeOptions.content) {
                    this.content(shapeOptions.content);
                }
            }
        },

        _diffNumericOptions: diagram.diffNumericOptions,

        _visualOptions: function (options) {
            return {
                data: options.path,
                source: options.source,
                hover: options.hover,
                fill: options.fill,
                stroke: options.stroke,
                startCap: options.startCap,
                endCap: options.endCap
            };
        },

        _triggerBoundsChange: function () {
            if (this.diagram) {
                this.diagram.trigger(ITEMBOUNDSCHANGE, { item: this, bounds: this._bounds.clone() }); // the trigger modifies the arguments internally.
            }
        },

        _transformPoint: function (point) {
            var rotate = this.rotate(),
                    bounds = this.bounds(),
                    tl = bounds.topLeft();

            if (rotate.angle) {
                point.rotate(rotate.center().plus(tl), 360 - rotate.angle);
            }

            return point;
        },

        _transformedBounds: function () {
            var bounds = this.bounds(),
                    tl = bounds.topLeft(),
                    br = bounds.bottomRight();

            return Rect.fromPoints(this.diagram.modelToView(tl), this.diagram.modelToView(br));
        },

        _rotatedBounds: function () {
            var bounds = this.bounds().rotatedBounds(this.rotate().angle),
                    tl = bounds.topLeft(),
                    br = bounds.bottomRight();

            return Rect.fromPoints(tl, br);
        },

        _rotate: function () {
            var rotation = this.options.rotation;

            if (rotation && rotation.angle) {
                this.rotate(rotation.angle);
            }

            this._rotationOffset = new Point();
        },

        _hover: function (value) {
            var options = this.options,
                    hover = options.hover,
                    stroke = options.stroke,
                    fill = options.fill;

            if (value && isDefined(hover.stroke)) {
                stroke = deepExtend({}, stroke, hover.stroke);
            }

            if (value && isDefined(hover.fill)) {
                fill = hover.fill;
            }

            this.shapeVisual.redraw({
                stroke: stroke,
                fill: fill
            });

            if (options.editable && options.editable.connect) {
                this.diagram._showConnectors(this, value);
            }
        },

        _hitTest: function (value) {
            if (this.visible()) {
                var bounds = this.bounds(), rotatedPoint,
                        angle = this.rotate().angle;

                if (value.isEmpty && !value.isEmpty()) { // rect selection
                    return Intersect.rects(value, bounds, angle ? angle : 0);
                } else { // point
                    rotatedPoint = value.clone().rotate(bounds.center(), angle); // cloning is important because rotate modifies the point inline.
                    if (bounds.contains(rotatedPoint)) {
                        return this;
                    }
                }
            }
        },
        toJSON: function () {
            return {
                shapeId: this.options.id
            };
        }
    });

    Shape.createShapeVisual = function (options) {
        var diagram = options.diagram;
        delete options.diagram; // avoid stackoverflow and reassign later on again
        var shapeDefaults = deepExtend({}, options, { x: 0, y: 0 }),
                visualTemplate = shapeDefaults.visual, // Shape visual should not have position in its parent group.
                type = shapeDefaults.type;

        function simpleShape(name, shapeDefaults) {
            switch (name.toLocaleLowerCase()) {
                case "rectangle":
                    return new Rectangle(shapeDefaults);
                case "circle":
                    return new Circle(shapeDefaults);
                case "text": // Maybe should be something else.
                    return new TextBlock(shapeDefaults);
                case "image":
                    return new Image(shapeDefaults);
                default:
                    var p = new Path(shapeDefaults);
                    return p;
            }
        }

        function pathShape(path, shapeDefaults) {
            shapeDefaults.data = path;

            return new Path(shapeDefaults);
        }

        function functionShape(func, context, shapeDefaults) {
            return func.call(context, shapeDefaults);
        }

        if (isFunction(visualTemplate)) { // custom template
            return functionShape(visualTemplate, this, shapeDefaults);
        } else if (shapeDefaults.path) {
            return pathShape(shapeDefaults.path, shapeDefaults);
        } else if (isString(type)) {
            return simpleShape(shapeDefaults.type.toLocaleLowerCase(), shapeDefaults);
        } else {
            return new Rectangle(shapeDefaults);
        }
    };

    /**
    * The visual link between two Shapes through the intermediate of Connectors.
    */
    var Connection = DiagramElement.extend({
        init: function (from, to, options) {
            var that = this;
            DiagramElement.fn.init.call(that, options);
            this.updateOptionsFromModel();
            this._initRouter();
            that.path = new diagram.Polyline(that.options);
            that.path.fill(TRANSPARENT);
            that.visual.append(that.path);
            that._sourcePoint = that._targetPoint = new Point();
            that.source(from);
            that.target(to);
            that.content(that.options.content);
            that.definers = [];
            if (defined(options) && options.points) {
                that.points(options.points);
            }
            that.refresh();
        },

        options: {
            hover: {
                stroke: {}
            },
            startCap: NONE,
            endCap: NONE,
            points: [],
            selectable: true
        },

        updateOptionsFromModel: function (model) {
            if (this.diagram && this.diagram._isEditable) {
                var options = filterConnectionDataItem(model || this.options.dataItem);

                if (model) {
                    if (defined(options.from)) {
                        this.source(options.from);
                    } else if (defined(options.fromX) && defined(options.fromY)) {
                        this.source(new Point(options.fromX, options.fromY));
                    }

                    if (defined(options.to)) {
                        this.target(options.to);
                    } else if (defined(options.toX) && defined(options.toY)) {
                        this.target(new Point(options.toX, options.toY));
                    }

                    this.options.dataItem = model;

                    this._template();
                    this.redraw(this.options);
                } else {
                    this.options = deepExtend({}, options, this.options);
                }
            }
        },

        updateModel: function (shouldRefresh) {
            if (this.diagram && this.diagram._isEditable) {
                if (this.diagram.connectionsDataSource) {
                    var model = this.diagram.connectionsDataSource.getByUid(this.options.dataItem.uid);
                    if (model) {
                        this.diagram._shouldRefresh = false;
                        if (defined(this.options.fromX) && this.options.fromX !== null) {
                            model._set("from", null);
                            model._set("fromX", this.options.fromX);
                            model._set("fromY", this.options.fromY);
                        } else {
                            model._set("from", this.options.from);
                            model._set("fromX", null);
                            model._set("fromY", null);
                        }

                        if (defined(this.options.toX) && this.options.toX !== null) {
                            model._set("to", null);
                            model._set("toX", this.options.toX);
                            model._set("toY", this.options.toY);
                        } else {
                            model._set("to", this.options.to);
                            model._set("toX", null);
                            model._set("toY", null);
                        }

                        this.dataItem = this.options.dataItem = model;

                        if (shouldRefresh !== false) {
                            this.diagram._shouldRefresh = true;
                            model.trigger("change");
                        }
                    }
                }
            }
        },

        /**
        * Gets the Point where the source of the connection resides.
        * If the endpoint in Auto-connector the location of the resolved connector will be returned.
        * If the endpoint is floating the location of the endpoint is returned.
        */
        sourcePoint: function () {
            return this._resolvedSourceConnector ? this._resolvedSourceConnector.position() : this._sourcePoint;
        },

        /**
        * Gets or sets the Point where the source of the connection resides.
        * @param source The source of this connection. Can be a Point, Shape, Connector.
        * @param undoable Whether the change or assignment should be undoable.
        */
        source: function (source, undoable) {
            var dataItem;
            if (isDefined(source)) {
                if (undoable && this.diagram) {
                    this.diagram.undoRedoService.addCompositeItem(new diagram.ConnectionEditUnit(this, source));
                } else {
                    if (source !== undefined) {
                        this.from = source;
                    }
                    if (source === null) { // detach
                        if (this.sourceConnector) {
                            this._sourcePoint = this._resolvedSourceConnector.position();
                            this._clearSourceConnector();
                        }
                    } else if (source instanceof Connector) {
                        dataItem = source.shape.options.dataItem;
                        if (dataItem) {
                            this.options.from = dataItem.id;
                            this.options.fromX = null;
                            this.options.fromY = null;
                        }
                        this.sourceConnector = source;
                        this.sourceConnector.connections.push(this);

                    } else if (source instanceof Point) {
                        this.options.fromX = source.x;
                        this.options.fromY = source.y;
                        this.options.from = null;
                        this._sourcePoint = source;
                        if (this.sourceConnector) {
                            this._clearSourceConnector();
                        }

                    } else if (source instanceof Shape) {
                        dataItem = source.options.dataItem;
                        if (dataItem) {
                            this.options.from = dataItem.id;
                            this.options.fromX = null;
                            this.options.fromY = null;
                        }
                        this.sourceConnector = source.getConnector(AUTO); // source.getConnector(this.targetPoint());
                        this.sourceConnector.connections.push(this);
                    }

                    this.refresh();
                }
            }
            return this.sourceConnector ? this.sourceConnector : this._sourcePoint;
        },

        /**
        * Gets or sets the PathDefiner of the sourcePoint.
        * The left part of this definer is always null since it defines the source tangent.
        * @param value
        * @returns {*}
        */
        sourceDefiner: function (value) {
            if (value) {
                if (value instanceof diagram.PathDefiner) {
                    value.left = null;
                    this._sourceDefiner = value;
                    this.source(value.point); // refresh implicit here
                } else {
                    throw "The sourceDefiner needs to be a PathDefiner.";
                }
            } else {
                if (!this._sourceDefiner) {
                    this._sourceDefiner = new diagram.PathDefiner(this.sourcePoint(), null, null);
                }
                return this._sourceDefiner;
            }
        },

        /**
        * Gets  the Point where the target of the connection resides.
        */
        targetPoint: function () {
            return this._resolvedTargetConnector ? this._resolvedTargetConnector.position() : this._targetPoint;
        },
        /**
        * Gets or sets the Point where the target of the connection resides.
        * @param target The target of this connection. Can be a Point, Shape, Connector.
        * @param undoable  Whether the change or assignment should be undoable.
        */
        target: function (target, undoable) {
            var dataItem;
            if (isDefined(target)) {
                if (undoable && this.diagram) {
                    this.diagram.undoRedoService.addCompositeItem(new diagram.ConnectionEditUnit(this, target));
                } else {
                    if (target !== undefined) {
                        this.to = target;
                    }

                    if (target === null) { // detach
                        if (this.targetConnector) {
                            this._targetPoint = this._resolvedTargetConnector.position();
                            this._clearTargetConnector();
                        }
                    } else if (target instanceof Connector) {
                        dataItem = target.shape.options.dataItem;
                        if (dataItem) {
                            this.options.to = dataItem.id;
                            this.options.toX = null;
                            this.options.toY = null;
                        }
                        this.targetConnector = target;
                        this.targetConnector.connections.push(this);
                    } else if (target instanceof Point) {
                        this.options.toX = target.x;
                        this.options.toY = target.y;
                        this.options.to = null;
                        this._targetPoint = target;
                        if (this.targetConnector) {
                            this._clearTargetConnector();
                        }
                    } else if (target instanceof Shape) {
                        dataItem = target.options.dataItem;
                        if (dataItem) {
                            this.options.to = dataItem.id;
                            this.options.toX = null;
                            this.options.toY = null;
                        }
                        this.targetConnector = target.getConnector(AUTO); // target.getConnector(this.sourcePoint());
                        this.targetConnector.connections.push(this);
                    }
                }

                this.refresh();
            }
            return this.targetConnector ? this.targetConnector : this._targetPoint;
        },
        /**
        * Gets or sets the PathDefiner of the targetPoint.
        * The right part of this definer is always null since it defines the target tangent.
        * @param value
        * @returns {*}
        */
        targetDefiner: function (value) {
            if (value) {
                if (value instanceof diagram.PathDefiner) {
                    value.right = null;
                    this._targetDefiner = value;
                    this.target(value.point); // refresh implicit here
                } else {
                    throw "The sourceDefiner needs to be a PathDefiner.";
                }
            } else {
                if (!this._targetDefiner) {
                    this._targetDefiner = new diagram.PathDefiner(this.targetPoint(), null, null);
                }
                return this._targetDefiner;
            }
        },

        content: function (content) {
            return this._content(content);
        },

        /**
        * Selects or unselects this connections.
        * @param value True to select, false to unselect.
        */
        select: function (value) {
            var diagram = this.diagram, selected, deselected;
            if (this._canSelect()) {
                if (this.isSelected !== value) {
                    this.isSelected = value;
                    selected = [];
                    deselected = [];
                    if (this.isSelected) {
                        this.adorner = new ConnectionEditAdorner(this, this.options.selection);
                        diagram._adorn(this.adorner, true);
                        diagram._selectedItems.push(this);
                        selected.push(this);
                    } else {
                        if (this.adorner) {
                            diagram._adorn(this.adorner, false);
                            Utils.remove(diagram._selectedItems, this);
                            this.adorner = undefined;
                            deselected.push(this);
                        }
                    }
                    this.refresh();
                    if (!diagram._internalSelection) {
                        diagram._selectionChanged(selected, deselected);
                    }
                    return true;
                }
            }
        },
        /**
        * Gets or sets the bounds of this connection.
        * @param value A Rect object.
        * @remark This is automatically set in the refresh().
        * @returns {Rect}
        */
        bounds: function (value) {
            if (value && !isString(value)) {
                this._bounds = value;
            } else {
                return this._bounds;
            }
        },
        /**
        * Gets or sets the connection type (see ConnectionType enumeration).
        * @param value A ConnectionType value.
        * @returns {ConnectionType}
        */
        type: function (value) {
            var options = this.options;
            if (value) {
                if (value !== options.type) {
                    options.type = value;
                    this._initRouter();
                    this.refresh();
                }
            } else {
                return options.type;
            }
        },

        _initRouter: function () {
            var type = (this.options.type || "").toLowerCase();
            if (type == CASCADING) {
                this._router = new CascadingRouter(this);
            } else {
                this._router = new PolylineRouter(this);
            }
        },
        /**
        * Gets or sets the collection of *intermediate* points.
        * The 'allPoints()' property will return all the points.
        * The 'definers' property returns the definers of the intermediate points.
        * The 'sourceDefiner' and 'targetDefiner' return the definers of the endpoints.
        * @param value
        */
        points: function (value) {
            if (value) {
                this.definers = [];
                for (var i = 0; i < value.length; i++) {
                    var definition = value[i];
                    if (definition instanceof diagram.Point) {
                        this.definers.push(new diagram.PathDefiner(definition));
                    } else if (definition.hasOwnProperty("x") && definition.hasOwnProperty("y")) { // e.g. Clipboard does not preserve the Point definition and tunred into an Object
                        this.definers.push(new diagram.PathDefiner(new Point(definition.x, definition.y)));
                    } else {
                        throw "A Connection point needs to be a Point or an object with x and y properties.";
                    }
                }

            } else {
                var pts = [];
                if (isDefined(this.definers)) {
                    for (var k = 0; k < this.definers.length; k++) {
                        pts.push(this.definers[k].point);
                    }
                }
                return pts;
            }
        },
        /**
        * Gets all the points of this connection. This is the combination of the sourcePoint, the points and the targetPoint.
        * @returns {Array}
        */
        allPoints: function () {
            var pts = [this.sourcePoint()];
            if (this.definers) {
                for (var k = 0; k < this.definers.length; k++) {
                    pts.push(this.definers[k].point);
                }
            }
            pts.push(this.targetPoint());
            return pts;
        },
        refresh: function () {
            resolveConnectors(this);
            var globalSourcePoint = this.sourcePoint(), globalSinkPoint = this.targetPoint(),
                    boundsTopLeft, localSourcePoint, localSinkPoint, middle;

            this._refreshPath();

            boundsTopLeft = this._bounds.topLeft();
            localSourcePoint = globalSourcePoint.minus(boundsTopLeft);
            localSinkPoint = globalSinkPoint.minus(boundsTopLeft);
            if (this._contentVisual) {
                middle = Point.fn.middleOf(localSourcePoint, localSinkPoint);
                this._contentVisual.position(new Point(middle.x + boundsTopLeft.x, middle.y + boundsTopLeft.y));
            }

            if (this.adorner) {
                this.adorner.refresh();
            }
        },

        redraw: function (options) {
            if (options) {
                this.options = deepExtend({}, this.options, options);

                var points = this.options.points;

                if ((options && options.content) || options.text) {
                    this.content(options.content);
                }

                if (defined(points) && points.length > 0) {
                    this.points(points);
                    this._refreshPath();
                }
                this.path.redraw({
                    fill: options.fill,
                    stroke: options.stroke,
                    startCap: options.startCap,
                    endCap: options.endCap
                });
            }
        },
        /**
        * Returns a clone of this connection.
        * @returns {Connection}
        */
        clone: function () {
            var json = this.serialize();

            if (this.diagram && this.diagram._isEditable && defined(this.options.dataItem)) {
                json.options.dataItem = this.options.dataItem.toJSON();
                json.options.dataItem[this.options.dataItem.idField] = this.options.dataItem._defaultId;
            }

            return new Connection(this.from, this.to, json.options);
        },
        /**
        * Returns a serialized connection in json format. Consist of the options and the dataItem.
        * @returns {Connection}
        */
        serialize: function () {
            var from = this.from.toJSON ? this.from.toJSON : this.from.toString(),
                    to = this.to.toJSON ? this.to.toJSON : this.to.toString();

            var json = deepExtend({}, {
                options: this.options,
                from: from,
                to: to
            });

            if (defined(this.dataItem)) {
                json.dataItem = this.dataItem.toString();
            }

            json.options.points = this.points();
            return json;
        },

        /**
        * Returns whether the given Point or Rect hits this connection.
        * @param value
        * @returns {Connection}
        * @private
        */
        _hitTest: function (value) {
            if (this.visible()) {
                var p = new Point(value.x, value.y), from = this.sourcePoint(), to = this.targetPoint();
                if (value.isEmpty && !value.isEmpty() && value.contains(from) && value.contains(to)) {
                    return this;
                }
                if (this._router.hitTest(p)) {
                    return this;
                }
            }
        },

        _hover: function (value) {
            var color = (this.options.stroke || {}).color;

            if (value && isDefined(this.options.hover.stroke.color)) {
                color = this.options.hover.stroke.color;
            }

            this.path.redraw({
                stroke: {
                    color: color
                }
            });
        },

        _refreshPath: function () {
            if (!defined(this.path)) {
                return;
            }
            this._drawPath();
            this.bounds(this._router.getBounds());
        },

        _drawPath: function () {
            if (this._router) {
                this._router.route(); // sets the intermediate points
            }
            var source = this.sourcePoint();
            var target = this.targetPoint();
            var points = this.points();

            this.path.redraw({
                points: [source].concat(points, [target])
            });
        },

        _clearSourceConnector: function () {
            Utils.remove(this.sourceConnector.connections, this);
            this.sourceConnector = undefined;
            this._resolvedSourceConnector = undefined;
        },
        _clearTargetConnector: function () {
            Utils.remove(this.targetConnector.connections, this);
            this.targetConnector = undefined;
            this._resolvedTargetConnector = undefined;
        }
    });

    var Diagram = Widget.extend({
        init: function (element, userOptions) {
            var that = this;

            kendo.destroy(element);
            Widget.fn.init.call(that, element, userOptions);

            that._initTheme();
            that._initElements();
            that._extendLayoutOptions(that.options);
            that._initShapeDefaults();

            that._initCanvas();

            that.mainLayer = new Group({
                id: "main-layer"
            });
            that.canvas.append(that.mainLayer);

            that._pan = new Point();
            that._adorners = [];
            that.adornerLayer = new Group({
                id: "adorner-layer"
            });
            that.canvas.append(that.adornerLayer);

            that._createHandlers();

            that._initialize();
            that._fetchFreshData();
            that._createGlobalToolBar();
            that._resizingAdorner = new ResizingAdorner(that, { editable: that.options.editable });
            that._connectorsAdorner = new ConnectorsAdorner(that);

            that._adorn(that._resizingAdorner, true);
            that._adorn(that._connectorsAdorner, true);

            that.selector = new Selector(that);
            // TODO: We may consider using real Clipboard API once is supported by the standard.
            that._clipboard = [];

            if (that.options.layout) {
                that.layout(that.options.layout);
            }
            that.pauseMouseHandlers = false;

            that._createShapes();
            that._createConnections();
            that.zoom(that.options.zoom);

            that.canvas.draw();
            this._shouldRefresh = true;
        },

        options: {
            name: "Diagram",
            theme: "default",
            layout: "",
            zoomRate: 0.1,
            zoom: 1,
            minZoom: 0,
            maxZoom: 2,
            dataSource: {},
            draggable: true,
            template: "",
            autoBind: true,
            editable: {
                rotate: {},
                resize: {},
                text: true,
                tools: []
            },
            pannable: {
                key: "ctrl"
            },
            selectable: {
                key: "none"
            },
            tooltip: { enabled: true, format: "{0}" },
            copy: {
                enabled: true,
                offsetX: 20,
                offsetY: 20
            },
            snap: {
                enabled: true,
                size: 10,
                angle: 10
            },
            shapeDefaults: diagram.shapeDefaults({ undoable: true }),
            connectionDefaults: {
                editable: {
                    tools: []
                }
            },
            shapes: [],
            connections: []
        },

        events: [
                ZOOM_END,
                ZOOM_START,
                PAN, SELECT,
                ITEMROTATE,
                ITEMBOUNDSCHANGE,
                CHANGE,
                CLICK,
                "toolBarClick",
                "save",
                "cancel",
                "edit",
                "remove",
                "add",
                "dataBound"
            ],

        _createGlobalToolBar: function () {
            var tools = this.options.editable.tools;
            if (this._isEditable && tools.length === 0) {
                tools = ["createShape", "undo", "redo", "rotateClockwise", "rotateAnticlockwise"];
            }

            if (tools && tools.length) {
                this.toolBar = new DiagramToolBar(this, {
                    tools: tools || {},
                    click: proxy(this._toolBarClick, this),
                    modal: false
                });

                this.toolBar.element.css({
                    position: "absolute",
                    top: 0,
                    left: 0,
                    width: this.element.width(),
                    textAlign: "left"
                });

                this.element.append(this.toolBar.element);
            }
        },

        createShape: function () {
            var that = this;
            if (((this.editor && this.editor.end()) || !this.editor) &&
                    !this.trigger("add", { shape: {} })) {
                var view = this.dataSource.view() || [];
                var index = view.length;
                var model = this.dataSource.insert(index, {});
                this.dataSource.one("sync", function () {
                    var shape = that._dataMap[model.id];
                    that.edit(shape);
                });
                this.dataSource.sync();
            }
        },

        createConnection: function () {
            if (((this.editor && this.editor.end()) || !this.editor) &&
                    !this.trigger("add", { connection: {} })) {
                var view = this.connectionsDataSource.view() || [];
                var index = view.length;
                var model = this.connectionsDataSource.insert(index, {});
                var connection = this._connectionsDataMap[model.uid];
                this.edit(connection);
            }
        },

        edit: function (item) {
            this.cancelEdit();
            var editorType, editors, template;
            var editable = this.options.editable;

            if (item instanceof Shape) {
                editorType = "shape";
                editors = editable.shapeEditors;
                template = editable.shapeTemplate;
            } else if (item instanceof Connection) {
                editorType = "connection";
                var connectionSelectorHandler = proxy(connectionSelector, this);
                editors = deepExtend({}, { from: connectionSelectorHandler, to: connectionSelectorHandler }, editable.connectionEditors);
                template = editable.connectionTemplate;
            } else {
                return;
            }

            if (item.options.dataItem) {
                this.editor = new PopupEditor(this.element, {
                    update: proxy(this._update, this),
                    cancel: proxy(this._cancel, this),
                    model: item.dataItem,
                    type: editorType,
                    target: this,
                    editors: editors,
                    template: template
                });

                this.trigger("edit", this._editArgs());
            }
        },

        cancelEdit: function () {
            if (this.editor) {
                this._getEditDataSource().cancelChanges(this.editor.model);

                this._destroyEditor();
            }
        },

        saveEdit: function () {
            if (this.editor && this.editor.end() &&
                    !this.trigger("save", this._editArgs())) {
                this._getEditDataSource().sync();
            }
        },

        _update: function () {
            if (this.editor && this.editor.end() &&
                    !this.trigger("save", this._editArgs())) {
                this._getEditDataSource().sync();
                this._destroyEditor();
            }
        },

        _cancel: function () {
            if (this.editor && !this.trigger("cancel", this._editArgs())) {
                this._getEditDataSource().cancelChanges(this.editor.model);
                this._destroyEditor();
            }
        },

        _getEditDataSource: function () {
            return this.editor.options.type === "shape" ? this.dataSource : this.connectionsDataSource;
        },

        _editArgs: function () {
            var result = { container: this.editor.element };
            result[this.editor.options.type] = this.editor.model;
            return result;
        },

        _destroyEditor: function () {
            if (this.editor) {
                this.editor.close();
                this.editor = null;
            }
        },

        _initElements: function () {
            this.wrapper = this.element.empty()
                    .css("position", "relative")
                    .attr("tabindex", 0)
                    .addClass("k-widget k-diagram");


            this.scrollable = $("<div />").appendTo(this.element);
        },

        _initShapeDefaults: function () {
            var options = this.options;
            if (options.editable === false) {
                deepExtend(options.shapeDefaults, {
                    editable: {
                        connect: false
                    }
                });
            }
        },

        _initCanvas: function () {
            var canvasContainer = $("<div class='k-layer'></div>").appendTo(this.scrollable)[0];
            var viewPort = this.viewport();
            this.canvas = new Canvas(canvasContainer, {
                width: viewPort.width || DEFAULT_CANVAS_WIDTH,
                height: viewPort.height || DEFAULT_CANVAS_HEIGHT
            });
        },

        _createHandlers: function () {
            var that = this;
            var element = that.element;

            element.on(MOUSEWHEEL_NS, proxy(that._wheel, that));
            if (!kendo.support.touch && !kendo.support.mobileOS) {
                that.toolService = new ToolService(that);
                this.scroller.wrapper
                        .on("mousemove" + NS, proxy(that._mouseMove, that))
                        .on("mouseup" + NS, proxy(that._mouseUp, that))
                        .on("mousedown" + NS, proxy(that._mouseDown, that))
                        .on("mouseover" + NS, proxy(that._mouseover, that))
                        .on("mouseout" + NS, proxy(that._mouseout, that));

                element.on("keydown" + NS, proxy(that._keydown, that));
            } else {
                that._userEvents = new kendo.UserEvents(element, {
                    multiTouch: true,
                    tap: proxy(that._tap, that)
                });

                that._userEvents.bind(["gesturestart", "gesturechange", "gestureend"], {
                    gesturestart: proxy(that._gestureStart, that),
                    gesturechange: proxy(that._gestureChange, that),
                    gestureend: proxy(that._gestureEnd, that)
                });
                that.toolService = new ToolService(that);
                if (that.options.pannable !== false) {
                    that.scroller.enable();
                }
            }

            that._resizeHandler = proxy(that.resize, that);
            kendo.onResize(that._resizeHandler);
            this.bind(ZOOM_START, proxy(that._destroyToolBar, that));
            this.bind(PAN, proxy(that._destroyToolBar, that));
        },

        _tap: function (e) {
            var toolService = this.toolService;
            var p = this._caculateMobilePosition(e);
            toolService._updateHoveredItem(p);
            if (toolService.hoveredItem) {
                var item = toolService.hoveredItem;
                if (this.options.selectable !== false) {
                    this._destroyToolBar();
                    if (item.isSelected) {
                        item.select(false);
                    } else {
                        this.select(item, { addToSelection: true });
                    }
                    this._createToolBar();
                }
                this.trigger("click", {
                    item: item,
                    point: p
                });
            }
        },

        _caculateMobilePosition: function (e) {
            return this.documentToModel(
                    Point(e.x.location, e.y.location)
                );
        },

        _gestureStart: function (e) {
            this._destroyToolBar();
            this.scroller.disable();
            this._gesture = e;
            this._initialCenter = this.documentToModel(new Point(e.center.x, e.center.y));
            this.trigger(ZOOM_START, {
                point: this._initialCenter,
                zoom: this.zoom()
            });
        },

        _gestureChange: function (e) {
            var previousGesture = this._gesture;
            var initialCenter = this._initialCenter;
            var center = this.documentToView(new Point(e.center.x, e.center.y));
            var scaleDelta = e.distance / previousGesture.distance;
            var zoom = this._zoom;
            var updateZoom = false;

            if (math.abs(scaleDelta - 1) >= MOBILE_ZOOM_RATE) {
                this._zoom = zoom = this._getValidZoom(zoom * scaleDelta);
                this.options.zoom = zoom;
                this._gesture = e;
                updateZoom = true;
            }

            var zoomedPoint = initialCenter.times(zoom);
            var pan = center.minus(zoomedPoint);
            if (updateZoom || this._pan.distanceTo(pan) >= MOBILE_PAN_DISTANCE) {
                this._panTransform(pan);
                this._updateAdorners();
            }

            e.preventDefault();
        },

        _gestureEnd: function () {
            if (this.options.pannable !== false) {
                this.scroller.enable();
            }
            this.trigger(ZOOM_END, {
                point: this._initialCenter,
                zoom: this.zoom()
            });
        },

        _resize: function (size) {
            if (this.canvas) {
                this.canvas.size(size);
            }

            if (this.toolBar) {
                this.toolBar._toolBar.element.width(this.element.width());
            }
        },

        _mouseover: function (e) {
            var node = e.target._kendoNode;
            if (node && node.srcElement._hover) {
                node.srcElement._hover(true, node.srcElement);
            }
        },

        _mouseout: function (e) {
            var node = e.target._kendoNode;
            if (node && node.srcElement._hover) {
                node.srcElement._hover(false, node.srcElement);
            }
        },

        _initTheme: function () {
            var that = this,
                    themes = dataviz.ui.themes || {},
                    themeName = ((that.options || {}).theme || "").toLowerCase(),
                    themeOptions = (themes[themeName] || {}).diagram;

            that.options = deepExtend({}, themeOptions, that.options);
        },

        _createShapes: function () {
            var that = this,
                    options = that.options,
                    shapes = options.shapes,
                    shape, i;

            for (i = 0; i < shapes.length; i++) {
                shape = shapes[i];
                that.addShape(shape);
            }
        },

        _createConnections: function () {
            var diagram = this,
                    options = diagram.options,
                    defaults = options.connectionDefaults,
                    connections = options.connections,
                    conn, source, target, i;

            for (i = 0; i < connections.length; i++) {
                conn = connections[i];
                source = diagram._findConnectionShape(conn.from);
                target = diagram._findConnectionShape(conn.to);

                diagram.connect(source, target, deepExtend({}, defaults, conn));
            }
        },

        _findConnectionShape: function (options) {
            var diagram = this,
                    shapeId = isString(options) ? options : options.shapeId;

            var shape = diagram.getShapeById(shapeId);

            return shape.getConnector(options.connector || AUTO);
        },

        destroy: function () {
            var that = this;
            Widget.fn.destroy.call(that);

            if (this._userEvents) {
                this._userEvents.destroy();
            }

            kendo.unbindResize(that._resizeHandler);

            that.clear();
            that.element.off(NS);
            that.scroller.wrapper.off(NS);
            that.canvas.destroy(true);
            that.canvas = undefined;

            that._destroyEditor();
            that.destroyScroller();
            that._destroyGlobalToolBar();
            that._destroyToolBar();
        },

        destroyScroller: function () {
            var scroller = this.scroller;

            if (!scroller) {
                return;
            }

            scroller.destroy();
            scroller.element.remove();
            this.scroller = null;
        },

        save: function () {
            var json = {}, i;

            json.shapes = [];
            json.connections = [];

            for (i = 0; i < this.shapes.length; i++) {
                var shape = this.shapes[i];
                if (shape.options.serializable) {
                    json.shapes.push(shape.options);
                }
            }

            for (i = 0; i < this.connections.length; i++) {
                var con = this.connections[i];
                var conOptions = deepExtend({}, con.options, { from: con.from.toJSON(), to: con.to.toJSON() });
                json.connections.push(conOptions);
            }

            return json;
        },

        focus: function () {
            if (!this.element.is(kendo._activeElement())) {
                var element = this.element,
                        scrollContainer = element[0],
                        containers = [],
                        offsets = [],
                        documentElement = document.documentElement,
                        i;

                do {
                    scrollContainer = scrollContainer.parentNode;

                    if (scrollContainer.scrollHeight > scrollContainer.clientHeight) {
                        containers.push(scrollContainer);
                        offsets.push(scrollContainer.scrollTop);
                    }
                } while (scrollContainer != documentElement);

                element.focus();

                for (i = 0; i < containers.length; i++) {
                    containers[i].scrollTop = offsets[i];
                }
            }
        },

        load: function (options) {
            this.clear();

            this.setOptions(options);
            this._createShapes();
            this._createConnections();
        },

        setOptions: function (options) {
            deepExtend(this.options, options);
        },

        clear: function () {
            var that = this;

            that.select(false);
            that.mainLayer.clear();
            that._initialize();
        },
        /**
        * Connects two items.
        * @param source Shape, Connector, Point.
        * @param target Shape, Connector, Point.
        * @param options Connection options that will be passed to the newly created connection.
        * @returns The newly created connection.
        */
        connect: function (source, target, options) {
            var connection;
            if (this.connectionsDataSource && this._isEditable) {
                var dataItem = this.connectionsDataSource.add({});
                connection = this._connectionsDataMap[dataItem.uid];
                connection.source(source);
                connection.target(target);
                this.connectionsDataSource.sync();
            } else {
                connection = new Connection(source, target,
                        deepExtend({}, this.options.connectionDefaults, options));

                this.addConnection(connection);
            }

            return connection;
        },
        /**
        * Determines whether the the two items are connected.
        * @param source Shape, Connector, Point.
        * @param target Shape, Connector, Point.
        * @returns true if the two items are connected.
        */
        connected: function (source, target) {
            for (var i = 0; i < this.connections.length; i++) {
                var c = this.connections[i];
                if (c.from == source && c.to == target) {
                    return true;
                }
            }

            return false;
        },
        /**
        * Adds connection to the diagram.
        * @param connection Connection.
        * @param undoable Boolean.
        * @returns The newly created connection.
        */
        addConnection: function (connection, undoable) {
            if (undoable !== false) {
                this.undoRedoService.add(
                        new diagram.AddConnectionUnit(connection, this), false);
            }

            connection.diagram = this;
            this.mainLayer.append(connection.visual);
            this.connections.push(connection);
            connection.updateOptionsFromModel();

            return connection;
        },

        _addConnection: function (connection, undoable) {
            var that = this;
            if (this.connectionsDataSource && this._isEditable) {
                if (!this.trigger("add", { connection: connection.options.dataItem })) {
                    var dataItem = this.connectionsDataSource.add(connection.options.dataItem);
                    this.connectionsDataSource.one("sync", function () {
                        for (var i = 0; i < that.connections.length; i++) {
                            var element = that.connections[i];
                            if (element.options.dataItem.uid === dataItem.uid) {
                                element.redraw(connection.options);
                            }
                        }
                    });
                    connection.options.dataItem = dataItem;
                    this.connectionsDataSource.sync();
                } else {
                    this._remove(connection, false);
                }
            } else {
                return this.addConnection(connection, undoable);
            }
        },

        /**
        * Adds shape to the diagram.
        * @param item Shape, Point. If point is passed it will be created new Shape and positioned at that point.
        * @param options. The options to be passed to the newly created Shape.
        * @returns The newly created shape.
        */
        addShape: function (item, options) {
            var shape,
                    shapeDefaults = this.options.shapeDefaults;

            if (item instanceof Shape) {
                shapeDefaults = deepExtend({}, shapeDefaults, options);
                item.redraw(options);
                shape = item;
            } else if (!(item instanceof kendo.Class)) {
                shapeDefaults = deepExtend({}, shapeDefaults, item || {});
                shape = new Shape(shapeDefaults);
            } else {
                return;
            }

            if (shapeDefaults.undoable) {
                this.undoRedoService.add(new diagram.AddShapeUnit(shape, this), false);
            }

            this.shapes.push(shape);
            shape.diagram = this;
            this.mainLayer.append(shape.visual);

            this.trigger(CHANGE, {
                added: [shape],
                removed: []
            });

            // for shapes which have their own internal layout mechanism
            if (shape.hasOwnProperty("layout")) {
                shape.layout(shape);
            }

            return shape;
        },

        _addShape: function (shape, options) {
            var that = this;
            if (this.dataSource && this._isEditable) {
                if (!this.trigger("add", { shape: shape.options.dataItem })) {
                    var dataItem = this.dataSource.add(shape.options.dataItem);
                    this.dataSource.one("sync", function () {
                        for (var i = 0; i < that.shapes.length; i++) {
                            var element = that.shapes[i];
                            if (element.options.dataItem.uid === dataItem.uid) {
                                element.redraw(shape.options);
                            }
                        }
                    });
                    shape.options.dataItem = dataItem;
                    this.dataSource.sync();
                } else {
                    this._remove(shape, false);
                }
            } else {
                return this.addShape(shape, options);
            }
        },
        /**
        * Removes items (or single item) from the diagram.
        * @param items DiagramElement, Array of Items.
        * @param undoable.
        */
        remove: function (items, undoable) {
            var isMultiple = isArray(items);

            if (isUndefined(undoable)) {
                undoable = true;
            }

            if (undoable) {
                this.undoRedoService.begin();
            }

            if (isMultiple) {
                items = items.slice(0);
                for (var i = 0; i < items.length; i++) {
                    this._removeItem(items[i], undoable);
                }
            } else if (items instanceof Shape || items instanceof Connection) {
                this._removeItem(items, undoable);
            }

            if (undoable) {
                this.undoRedoService.commit();
            }

            this.trigger(CHANGE, {
                added: [],
                removed: isMultiple ? items : [items]
            });
        },

        _remove: function (item, undoable) {
            var eventArgs = {};
            var dataSource;
            if (this._isEditable) {
                if (item.length) {
                    this._destroyToolBar();
                } else {
                    if (item instanceof Connection) {
                        if (this.connectionsDataSource) {
                            dataSource = this.connectionsDataSource;
                            eventArgs.connection = item.options.dataItem;
                        }
                    } else if (this.dataSource) {
                        dataSource = this.dataSource;
                        eventArgs.shape = item.options.dataItem;
                    }

                    if (dataSource && !this.trigger("remove", eventArgs)) {
                        dataSource.remove(item.options.dataItem);
                        dataSource.sync();
                    }
                }
            }

            this.remove(item, undoable);
        },

        /**
        * Executes the next undoable action on top of the undo stack if any.
        */
        undo: function () {
            this.undoRedoService.undo();
        },
        /**
        * Executes the previous undoable action on top of the redo stack if any.
        */
        redo: function () {
            this.undoRedoService.redo();
        },
        /**
        * Selects items on the basis of the given input or returns the current selection if none.
        * @param itemsOrRect DiagramElement, Array of elements, "All", false or Rect. A value 'false' will deselect everything.
        * @param options
        * @returns {Array}
        */
        select: function (item, options) {
            if (isDefined(item)) {
                options = deepExtend({ addToSelection: false }, options);

                var addToSelection = options.addToSelection,
                        items = [],
                        selected = [],
                        i, element;

                if (!addToSelection) {
                    this.deselect();
                }

                this._internalSelection = true;

                if (item instanceof Array) {
                    items = item;
                } else if (item instanceof DiagramElement) {
                    items = [item];
                }

                for (i = 0; i < items.length; i++) {
                    element = items[i];
                    if (element.select(true)) {
                        selected.push(element);
                    }
                }

                this._selectionChanged(selected, []);

                this._internalSelection = false;
            } else {
                return this._selectedItems;
            }
        },

        selectAll: function () {
            this.select(this.shapes.concat(this.connections));
        },

        selectArea: function (rect) {
            var i, items, item;
            this._internalSelection = true;
            var selected = [];
            if (rect instanceof Rect) {
                items = this.shapes.concat(this.connections);
                for (i = 0; i < items.length; i++) {
                    item = items[i];
                    if ((!rect || item._hitTest(rect)) && item.options.enable) {
                        if (item.select(true)) {
                            selected.push(item);
                        }
                    }
                }
            }

            this._selectionChanged(selected, []);
            this._internalSelection = false;
        },

        deselect: function (item) {
            this._internalSelection = true;
            var deselected = [],
                    items = [],
                    element, i;

            if (item instanceof Array) {
                items = item;
            } else if (item instanceof DiagramElement) {
                items.push(item);
            } else if (!isDefined(item)) {
                items = this._selectedItems.slice(0);
            }

            for (i = 0; i < items.length; i++) {
                element = items[i];
                if (element.select(false)) {
                    deselected.push(element);
                }
            }

            this._selectionChanged([], deselected);
            this._internalSelection = false;
        },
        /**
        * Brings to front the passed items.
        * @param items DiagramElement, Array of Items.
        * @param undoable. By default the action is undoable.
        */
        toFront: function (items, undoable) {
            if (!items) {
                items = this._selectedItems.slice();
            }

            var result = this._getDiagramItems(items), indices;
            if (!defined(undoable) || undoable) {
                indices = indicesOfItems(this.mainLayer, result.visuals);
                var unit = new ToFrontUnit(this, items, indices);
                this.undoRedoService.add(unit);
            } else {
                this.mainLayer.toFront(result.visuals);
                this._fixOrdering(result, true);
            }
        },
        /**
        * Sends to back the passed items.
        * @param items DiagramElement, Array of Items.
        * @param undoable. By default the action is undoable.
        */
        toBack: function (items, undoable) {
            if (!items) {
                items = this._selectedItems.slice();
            }

            var result = this._getDiagramItems(items), indices;
            if (!defined(undoable) || undoable) {
                indices = indicesOfItems(this.mainLayer, result.visuals);
                var unit = new ToBackUnit(this, items, indices);
                this.undoRedoService.add(unit);
            } else {
                this.mainLayer.toBack(result.visuals);
                this._fixOrdering(result, false);
            }
        },
        /**
        * Bring into view the passed item(s) or rectangle.
        * @param items DiagramElement, Array of Items, Rect.
        * @param options. align - controls the position of the calculated rectangle relative to the viewport.
        * "Center middle" will position the items in the center. animate - controls if the pan should be animated.
        */
        bringIntoView: function (item, options) { // jQuery|Item|Array|Rect
            var viewport = this.viewport();
            var aligner = new diagram.RectAlign(viewport);
            var current, rect, original, newPan;

            if (viewport.width === 0 || viewport.height === 0) {
                return;
            }

            options = deepExtend({ animate: false, align: "center middle" }, options);
            if (options.align == "none") {
                options.align = "center middle";
            }

            if (item instanceof DiagramElement) {
                rect = item.bounds(TRANSFORMED);
            } else if (isArray(item)) {
                rect = this.boundingBox(item);
            } else if (item instanceof Rect) {
                rect = item.clone();
            }

            original = rect.clone();

            rect.zoom(this._zoom);
            this._storePan(new Point());

            if (rect.width > viewport.width || rect.height > viewport.height) {
                this._zoom = this._getValidZoom(math.min(viewport.width / original.width, viewport.height / original.height));
                rect = original.clone().zoom(this._zoom);
            }

            this._zoomMainLayer();

            current = rect.clone();
            aligner.align(rect, options.align);

            newPan = rect.topLeft().minus(current.topLeft());
            this.pan(newPan.times(-1), options.animate);
        },

        alignShapes: function (direction) {
            if (isUndefined(direction)) {
                direction = "Left";
            }
            var items = this.select(),
                    val,
                    item,
                    i;

            if (items.length === 0) {
                return;
            }

            switch (direction.toLowerCase()) {
                case "left":
                case "top":
                    val = MAX_VALUE;
                    break;
                case "right":
                case "bottom":
                    val = MIN_VALUE;
                    break;
            }

            for (i = 0; i < items.length; i++) {
                item = items[i];
                if (item instanceof Shape) {
                    switch (direction.toLowerCase()) {
                        case "left":
                            val = math.min(val, item.options.x);
                            break;
                        case "top":
                            val = math.min(val, item.options.y);
                            break;
                        case "right":
                            val = math.max(val, item.options.x);
                            break;
                        case "bottom":
                            val = math.max(val, item.options.y);
                            break;
                    }
                }
            }
            var undoStates = [];
            var shapes = [];
            for (i = 0; i < items.length; i++) {
                item = items[i];
                if (item instanceof Shape) {
                    shapes.push(item);
                    undoStates.push(item.bounds());
                    switch (direction.toLowerCase()) {
                        case "left":
                        case "right":
                            item.position(new Point(val, item.options.y));
                            break;
                        case "top":
                        case "bottom":
                            item.position(new Point(item.options.x, val));
                            break;
                    }
                }
            }
            var unit = new diagram.TransformUnit(shapes, undoStates);
            this.undoRedoService.add(unit, false);
        },

        zoom: function (zoom, options) {
            if (zoom) {
                var staticPoint = options ? options.point : new diagram.Point(0, 0);
                // var meta = options ? options.meta : 0;
                zoom = this._zoom = this._getValidZoom(zoom);

                if (!isUndefined(staticPoint)) {//Viewpoint vector is constant
                    staticPoint = new diagram.Point(math.round(staticPoint.x), math.round(staticPoint.y));
                    var zoomedPoint = staticPoint.times(zoom);
                    var viewportVector = this.modelToView(staticPoint);
                    var raw = viewportVector.minus(zoomedPoint); //pan + zoomed point = viewpoint vector
                    this._storePan(new diagram.Point(math.round(raw.x), math.round(raw.y)));
                }

                if (options) {
                    options.zoom = zoom;
                }

                this._panTransform();

                this._updateAdorners();
            }

            return this._zoom;
        },

        _getPan: function (pan) {
            var canvas = this.canvas;
            if (!canvas.translate) {
                pan = pan.plus(this._pan);
            }
            return pan;
        },

        pan: function (pan, animate) {
            if (pan instanceof Point) {
                var that = this;
                var scroller = that.scroller;
                pan = that._getPan(pan);
                pan = pan.times(-1);

                if (animate) {
                    scroller.animatedScrollTo(pan.x, pan.y, function () {
                        that._updateAdorners();
                    });
                } else {
                    scroller.scrollTo(pan.x, pan.y);
                    that._updateAdorners();
                }
            }
        },

        viewport: function () {
            var element = this.element;

            return new Rect(0, 0, element.width(), element.height());
        },
        copy: function () {
            if (this.options.copy.enabled) {
                this._clipboard = [];
                this._copyOffset = 1;
                for (var i = 0; i < this._selectedItems.length; i++) {
                    var item = this._selectedItems[i];
                    this._clipboard.push(item);
                }
            }
        },
        cut: function () {
            if (this.options.copy.enabled) {
                this._clipboard = [];
                this._copyOffset = 0;
                for (var i = 0; i < this._selectedItems.length; i++) {
                    var item = this._selectedItems[i];
                    this._clipboard.push(item);
                }
                this.remove(this._clipboard, true);
            }
        },
        paste: function () {
            var offsetX, offsetY, item, copied, connector, shape, i;
            if (this._clipboard.length > 0) {
                var mapping = new Dictionary();

                offsetX = this._copyOffset * this.options.copy.offsetX;
                offsetY = this._copyOffset * this.options.copy.offsetY;
                this.deselect();
                // first the shapes
                for (i = 0; i < this._clipboard.length; i++) {
                    item = this._clipboard[i];
                    if (item instanceof Connection) {
                        continue;
                    }
                    copied = item.clone();
                    mapping.set(item.id, copied.id);
                    copied.position(new Point(item.options.x + offsetX, item.options.y + offsetY));
                    copied.diagram = this;
                    this._addShape(copied);
                    copied.select();
                    copied.updateModel();
                }
                // then the connections
                for (i = 0; i < this._clipboard.length; i++) {
                    item = this._clipboard[i];
                    if (item instanceof Shape) {
                        continue;
                    }
                    copied = item.clone();
                    if (item.source() instanceof Connector) { // if Point then it's a floating end
                        connector = item.source();
                        if (mapping.containsKey(connector.shape.id)) { // occurs when an attached connection is pasted with unselected shape parents
                            shape = this.getShapeById(mapping.get(connector.shape.id));
                            copied.source(shape.getConnector(connector.options.name));
                        } else {
                            copied.source(new Point(item.sourcePoint().x + offsetX, item.sourcePoint().y + offsetY));
                        }
                    }
                    if (item.target() instanceof Connector) {
                        connector = item.target();
                        if (mapping.containsKey(connector.shape.id)) {
                            shape = this.getShapeById(mapping.get(connector.shape.id));
                            copied.target(shape.getConnector(connector.options.name));
                        } else {
                            copied.target(new Point(item.targetPoint().x + offsetX, item.targetPoint().y + offsetY));
                        }
                    }
                    copied.diagram = this;
                    this._addConnection(copied);
                    copied.position(new Point(item.options.x + offsetX, item.options.y + offsetY));
                    copied.select(true);
                    copied.updateModel();
                }

                this._copyOffset += 1;
            }
        },
        /**
        * Gets the bounding rectangle of the given items.
        * @param items DiagramElement, Array of elements.
        * @param origin Boolean. Pass 'true' if you need to get the bounding box of the shapes without their rotation offset.
        * @returns {Rect}
        */
        boundingBox: function (items, origin) {
            var rect = Rect.empty(), temp,
                    di = isDefined(items) ? this._getDiagramItems(items) : { shapes: this.shapes };
            if (di.shapes.length > 0) {
                var item = di.shapes[0];
                rect = item.bounds(ROTATED);
                for (var i = 1; i < di.shapes.length; i++) {
                    item = di.shapes[i];
                    temp = item.bounds(ROTATED);
                    if (origin === true) {
                        temp.x -= item._rotationOffset.x;
                        temp.y -= item._rotationOffset.y;
                    }
                    rect = rect.union(temp);
                }
            }
            return rect;
        },
        documentToView: function (point) {
            var containerOffset = this.element.offset();

            return new Point(point.x - containerOffset.left, point.y - containerOffset.top);
        },
        viewToDocument: function (point) {
            var containerOffset = this.element.offset();

            return new Point(point.x + containerOffset.left, point.y + containerOffset.top);
        },
        viewToModel: function (point) {
            return this._transformWithMatrix(point, this._matrixInvert);
        },
        modelToView: function (point) {
            return this._transformWithMatrix(point, this._matrix);
        },
        modelToLayer: function (point) {
            return this._transformWithMatrix(point, this._layerMatrix);
        },
        layerToModel: function (point) {
            return this._transformWithMatrix(point, this._layerMatrixInvert);
        },
        documentToModel: function (point) {
            var viewPoint = this.documentToView(point);
            if (!this.canvas.translate) {
                viewPoint.x = viewPoint.x + this.scroller.scrollLeft;
                viewPoint.y = viewPoint.y + this.scroller.scrollTop;
            }
            return this.viewToModel(viewPoint);
        },
        modelToDocument: function (point) {
            return this.viewToDocument(this.modelToView(point));
        },
        _transformWithMatrix: function (point, matrix) {
            var result = point;
            if (point instanceof Point) {
                if (matrix) {
                    result = matrix.apply(point);
                }
            }
            else {
                var tl = this._transformWithMatrix(point.topLeft(), matrix),
                        br = this._transformWithMatrix(point.bottomRight(), matrix);
                result = Rect.fromPoints(tl, br);
            }
            return result;
        },

        setDataSource: function (dataSource) {
            this.options.dataSource = dataSource;
            this._dataSource();
            if (this.options.autoBind) {
                this.dataSource.fetch();
            }
        },

        setConnectionsDataSource: function (dataSource) {
            this.options.connectionsDataSource = dataSource;
            this._connectionDataSource();
            if (this.options.autoBind) {
                this.connectionsDataSource.fetch();
            }
        },

        /**
        * Performs a diagram layout of the given type.
        * @param layoutType The layout algorithm to be applied (TreeLayout, LayeredLayout, SpringLayout).
        * @param options Layout-specific options.
        */
        layout: function (options) {
            this.isLayouting = true;
            // TODO: raise layout event?
            var type;
            if (isUndefined(options)) {
                options = this.options.layout;
            }
            if (isUndefined(options) || isUndefined(options.type)) {
                type = "Tree";
            }
            else {
                type = options.type;
            }
            var l;
            switch (type.toLowerCase()) {
                case "tree":
                    l = new diagram.TreeLayout(this);
                    break;

                case "layered":
                    l = new diagram.LayeredLayout(this);
                    break;

                case "forcedirected":
                case "force":
                case "spring":
                case "springembedder":
                    l = new diagram.SpringLayout(this);
                    break;
                default:
                    throw "Layout algorithm '" + type + "' is not supported.";
            }
            var initialState = new diagram.LayoutState(this);
            var finalState = l.layout(options);
            if (finalState) {
                var unit = new diagram.LayoutUndoUnit(initialState, finalState, options ? options.animate : null);
                this.undoRedoService.add(unit);
            }
            this.isLayouting = false;
        },
        /**
        * Gets a shape on the basis of its identifier.
        * @param id (string) the identifier of a shape.
        * @returns {Shape}
        */
        getShapeById: function (id) {
            var found;
            found = Utils.first(this.shapes, function (s) {
                return s.visual.id === id;
            });
            if (found) {
                return found;
            }
            found = Utils.first(this.connections, function (c) {
                return c.visual.id === id;
            });
            return found;
        },

        _extendLayoutOptions: function (options) {
            if (options.layout) {
                options.layout = deepExtend(diagram.LayoutBase.fn.defaultOptions || {}, options.layout);
            }
        },

        _selectionChanged: function (selected, deselected) {
            if (selected.length || deselected.length) {
                this.trigger(SELECT, { selected: selected, deselected: deselected });
            }
        },
        _getValidZoom: function (zoom) {
            return math.min(math.max(zoom, this.options.minZoom), this.options.maxZoom);
        },
        _panTransform: function (pos) {
            var diagram = this,
                    pan = pos || diagram._pan;

            if (diagram.canvas.translate) {
                diagram.scroller.scrollTo(pan.x, pan.y);
                diagram._zoomMainLayer();
            } else {
                diagram._storePan(pan);
                diagram._transformMainLayer();
            }
        },

        _finishPan: function () {
            this.trigger(PAN, { total: this._pan, delta: Number.NaN });
        },
        _storePan: function (pan) {
            this._pan = pan;
            this._storeViewMatrix();
        },
        _zoomMainLayer: function () {
            var zoom = this._zoom;

            var transform = new CompositeTransform(0, 0, zoom, zoom);
            transform.render(this.mainLayer);
            this._storeLayerMatrix(transform);
            this._storeViewMatrix();
        },
        _transformMainLayer: function () {
            var pan = this._pan,
                    zoom = this._zoom;

            var transform = new CompositeTransform(pan.x, pan.y, zoom, zoom);
            transform.render(this.mainLayer);
            this._storeLayerMatrix(transform);
            this._storeViewMatrix();
        },
        _storeLayerMatrix: function (canvasTransform) {
            this._layerMatrix = canvasTransform.toMatrix();
            this._layerMatrixInvert = canvasTransform.invert().toMatrix();
        },
        _storeViewMatrix: function () {
            var pan = this._pan,
                    zoom = this._zoom;

            var transform = new CompositeTransform(pan.x, pan.y, zoom, zoom);
            this._matrix = transform.toMatrix();
            this._matrixInvert = transform.invert().toMatrix();
        },
        _toIndex: function (items, indices) {
            var result = this._getDiagramItems(items);
            this.mainLayer.toIndex(result.visuals, indices);
            this._fixOrdering(result, false);
        },
        _fixOrdering: function (result, toFront) {
            var shapePos = toFront ? this.shapes.length - 1 : 0,
                    conPos = toFront ? this.connections.length - 1 : 0,
                    i, item;
            for (i = 0; i < result.shapes.length; i++) {
                item = result.shapes[i];
                Utils.remove(this.shapes, item);
                Utils.insert(this.shapes, item, shapePos);
            }
            for (i = 0; i < result.cons.length; i++) {
                item = result.cons[i];
                Utils.remove(this.connections, item);
                Utils.insert(this.connections, item, conPos);
            }
        },
        _getDiagramItems: function (items) {
            var i, result = {}, args = items;
            result.visuals = [];
            result.shapes = [];
            result.cons = [];

            if (!items) {
                args = this._selectedItems.slice();
            } else if (!isArray(items)) {
                args = [items];
            }

            for (i = 0; i < args.length; i++) {
                var item = args[i];
                if (item instanceof Shape) {
                    result.shapes.push(item);
                    result.visuals.push(item.visual);
                } else if (item instanceof Connection) {
                    result.cons.push(item);
                    result.visuals.push(item.visual);
                }
            }

            return result;
        },

        _removeItem: function (item, undoable) {
            item.select(false);
            if (item instanceof Shape) {
                this._removeShape(item, undoable);
            } else if (item instanceof Connection) {
                this._removeConnection(item, undoable);
            }

            if (!undoable) {
                this.mainLayer.remove(item.visual);
            }
        },
        _removeShape: function (shape, undoable) {
            var i, connection, connector,
                    sources = [], targets = [];
            this.toolService._removeHover();

            if (undoable) {
                this.undoRedoService.addCompositeItem(new DeleteShapeUnit(shape));
            }
            else {
                Utils.remove(this.shapes, shape);
            }
            for (i = 0; i < shape.connectors.length; i++) {
                connector = shape.connectors[i];
                for (var j = 0; j < connector.connections.length; j++) {
                    connection = connector.connections[j];
                    if (connection.sourceConnector == connector) {
                        sources.push(connection);
                    } else if (connection.targetConnector == connector) {
                        targets.push(connection);
                    }
                }
            }

            for (i = 0; i < sources.length; i++) {
                sources[i].source(null, undoable);
            }
            for (i = 0; i < targets.length; i++) {
                targets[i].target(null, undoable);
            }
        },
        _removeConnection: function (connection, undoable) {
            if (connection.sourceConnector) {
                Utils.remove(connection.sourceConnector.connections, connection);
            }
            if (connection.targetConnector) {
                Utils.remove(connection.targetConnector.connections, connection);
            }
            if (undoable) {
                this.undoRedoService.addCompositeItem(new DeleteConnectionUnit(connection));
            }
            else {
                Utils.remove(this.connections, connection);
            }
        },

        _removeDataItems: function (items, recursive) {
            var item, children, shape, idx;
            items = isArray(items) ? items : [items];

            while (items.length) {
                item = items.shift();
                shape = this._dataMap[item.uid];
                if (shape) {
                    this._removeShapeConnections(shape);
                    this._removeItem(shape, false);
                    delete this._dataMap[item.uid];
                    if (recursive && item.hasChildren && item.loaded()) {
                        children = item.children.data();
                        for (idx = 0; idx < children.length; idx++) {
                            items.push(children[idx]);
                        }
                    }
                }
            }
        },

        _removeShapeConnections: function (shape) {
            var connections = shape.connections();
            var idx;

            if (connections) {
                for (idx = 0; idx < connections.length; idx++) {
                    this._removeItem(connections[idx], false);
                }
            }
        },

        _addDataItem: function (dataItem, options) {
            if (!defined(dataItem)) {
                return;
            }

            var shape = this._dataMap[dataItem.id];
            if (shape) {
                return shape;
            }

            options = deepExtend({}, this.options.shapeDefaults, options);
            options.dataItem = dataItem;
            shape = new Shape(options);
            this.addShape(shape);
            this._dataMap[dataItem.id] = shape;
            return shape;
        },

        _addDataItemByUid: function (dataItem) {
            if (!defined(dataItem)) {
                return;
            }

            var shape = this._dataMap[dataItem.uid];
            if (shape) {
                return shape;
            }

            var options = deepExtend({}, this.options.shapeDefaults);
            options.dataItem = dataItem;
            shape = new Shape(options);
            this.addShape(shape);
            this._dataMap[dataItem.uid] = shape;
            return shape;
        },

        _addDataItems: function (items, parent) {
            var item, idx, shape, parentShape, connection;
            for (idx = 0; idx < items.length; idx++) {
                item = items[idx];
                shape = this._addDataItemByUid(item);
                parentShape = this._addDataItemByUid(parent);
                if (parentShape && !this.connected(parentShape, shape)) { // check if connected to not duplicate connections.
                    connection = this.connect(parentShape, shape);
                    connection.type(CASCADING);
                }
            }
        },

        _refreshSource: function (e) {
            var that = this,
                    node = e.node,
                    action = e.action,
                    items = e.items,
                    options = that.options,
                    idx;

            if (e.field) {
                return;
            }

            if (action == "remove") {
                this._removeDataItems(e.items, true);
            } else {
                if (!action && !node) {
                    that.clear();
                }

                this._addDataItems(items, node);

                for (idx = 0; idx < items.length; idx++) {
                    items[idx].load();
                }
            }

            if (options.layout) {
                that.layout(options.layout);
            }
        },

        _mouseDown: function (e) {
            var p = this._calculatePosition(e);
            if (e.which == 1 && this.toolService.start(p, this._meta(e))) {
                this._destroyToolBar();
                e.preventDefault();
            }
        },

        _addItem: function (item) {
            if (item instanceof Shape) {
                this.addShape(item);
            } else if (item instanceof Connection) {
                this.addConnection(item);
            }
        },

        _mouseUp: function (e) {
            var p = this._calculatePosition(e);
            if (e.which == 1 && this.toolService.end(p, this._meta(e))) {
                this._createToolBar();
                e.preventDefault();
            }
        },

        _createToolBar: function () {
            var diagram = this.toolService.diagram;

            if (!this.singleToolBar && diagram.select().length === 1) {
                var element = diagram.select()[0];
                if (element) {
                    var tools = element.options.editable.tools;
                    if (this._isEditable && tools.length === 0) {
                        if (element instanceof Shape) {
                            tools = ["edit", "rotateClockwise", "rotateAnticlockwise", "delete"];
                        } else if (element instanceof Connection) {
                            tools = ["edit", "delete"];
                        }
                    }

                    if (tools) {
                        var padding = 20;
                        var point;
                        this.singleToolBar = new DiagramToolBar(diagram, {
                            tools: tools,
                            click: proxy(this._toolBarClick, this),
                            modal: true
                        });
                        var toolBarElement = this.singleToolBar.element;
                        var popupWidth = this.singleToolBar._popup.element.outerWidth();
                        var popupHeight = this.singleToolBar._popup.element.outerHeight();
                        if (element instanceof Shape) {
                            var shapeBounds = this.modelToView(element.bounds(ROTATED));
                            point = Point(shapeBounds.x, shapeBounds.y).minus(Point(
                                    (popupWidth - shapeBounds.width) / 2,
                                    popupHeight + padding));
                        } else if (element instanceof Connection) {
                            var connectionBounds = this.modelToView(element.bounds());

                            point = Point(connectionBounds.x, connectionBounds.y)
                                    .minus(Point(
                                        (popupWidth - connectionBounds.width - 20) / 2,
                                        popupHeight + padding
                                    ));
                        }

                        if (point) {
                            if (!this.canvas.translate) {
                                point = point.minus(Point(this.scroller.scrollLeft, this.scroller.scrollTop));
                            }
                            point = this.viewToDocument(point);
                            point = Point(math.max(point.x, 0), math.max(point.y, 0));
                            this.singleToolBar.showAt(point);
                        } else {
                            this._destroyToolBar();
                        }
                    }
                }
            }
        },

        _toolBarClick: function (e) {
            this.trigger("toolBarClick", e);
            this._destroyToolBar();
        },

        _mouseMove: function (e) {
            if (this.pauseMouseHandlers) {
                return;
            }
            var p = this._calculatePosition(e);
            if ((e.which === 0 || e.which == 1) && this.toolService.move(p, this._meta(e))) {
                e.preventDefault();
            }
        },

        _keydown: function (e) {
            if (this.toolService.keyDown(e.keyCode, this._meta(e))) {
                e.preventDefault();
            }
        },

        _wheel: function (e) {
            var delta = mwDelta(e),
                    p = this._calculatePosition(e),
                    meta = deepExtend(this._meta(e), { delta: delta });

            if (this.toolService.wheel(p, meta)) {
                e.preventDefault();
            }
        },

        _meta: function (e) {
            return { ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey, shiftKey: e.shiftKey };
        },

        _calculatePosition: function (e) {
            var pointEvent = (e.pageX === undefined ? e.originalEvent : e),
                    point = new Point(pointEvent.pageX, pointEvent.pageY),
                    offset = this.documentToModel(point);

            return offset;
        },

        _normalizePointZoom: function (point) {
            return point.times(1 / this.zoom());
        },

        _initialize: function () {
            this.shapes = [];
            this._selectedItems = [];
            this.connections = [];
            this._dataMap = {};
            this._connectionsDataMap = {};
            this._inactiveShapeItems = [];
            this._inactiveConnectionItems = [];
            this.undoRedoService = new UndoRedoService();
            this.id = diagram.randomId();
        },

        _fetchFreshData: function () {
            var that = this;
            that._dataSource();

            if (that._isEditable) {
                that._connectionDataSource();
            }

            if (that.options.autoBind) {
                if (that._isEditable) {
                    that._preventRefresh = true;
                    that._preventConnectionsRefresh = true;

                    var promises = $.map([
                            that.dataSource,
                            that.connectionsDataSource
                        ],
                        function (dataSource) {
                            return dataSource.fetch();
                        });

                    $.when.apply(null, promises)
                            .done(function () {
                                that._preventRefresh = false;
                                that._preventConnectionsRefresh = false;
                                that.refresh();
                            });
                } else {
                    that.dataSource.fetch();
                }
            }
        },

        _dataSource: function () {
            if (defined(this.options.connectionsDataSource)) {
                this._isEditable = true;
                var dsOptions = this.options.dataSource || {};
                var ds = isArray(dsOptions) ? { data: dsOptions} : dsOptions;

                if (this.dataSource && this._shapesRefreshHandler) {
                    this.dataSource
                            .unbind("change", this._shapesRefreshHandler)
                            .unbind("error", this._shapesErrorHandler);
                } else {
                    this._shapesRefreshHandler = proxy(this._refreshShapes, this);
                    this._shapesErrorHandler = proxy(this._error, this);
                }

                this.dataSource = kendo.data.DataSource.create(ds)
                        .bind("change", this._shapesRefreshHandler)
                        .bind("error", this._shapesErrorHandler);
            } else {
                this._treeDataSource();
                this._isEditable = false;
            }
        },

        _connectionDataSource: function () {
            var dsOptions = this.options.connectionsDataSource;
            if (dsOptions) {
                var ds = isArray(dsOptions) ? { data: dsOptions} : dsOptions;

                if (this.connectionsDataSource && this._connectionsRefreshHandler) {
                    this.connectionsDataSource
                            .unbind("change", this._connectionsRefreshHandler)
                            .unbind("error", this._connectionsErrorHandler);
                } else {
                    this._connectionsRefreshHandler = proxy(this._refreshConnections, this);
                    this._connectionsErrorHandler = proxy(this._error, this);
                }

                this.connectionsDataSource = kendo.data.DataSource.create(ds)
                        .bind("change", this._connectionsRefreshHandler)
                        .bind("error", this._connectionsErrorHandler);
            }
        },

        _refreshShapes: function (e) {
            if (e.action === "remove") {
                this._removeShapes(e.items);
            } else if (e.action === "itemchange") {
                this._updateShapes(e.items, e.field);
            } else if (e.action === "add") {
                this._inactiveShapeItems = this._inactiveShapeItems.concat(e.items);
            } else if (e.action === "sync") {
                this._syncShapes(e.items);
            } else {
                this.refresh();
            }
        },

        refresh: function () {
            if (this._preventRefresh) {
                return;
            }

            this.trigger("dataBound");
            this.clear();
            this._addShapes(this.dataSource.view());
            if (this.connectionsDataSource) {
                this._addConnections(this.connectionsDataSource.view());
            }

            if (this.options.layout) {
                this.layout(this.options.layout);
            }
        },

        refreshConnections: function () {
            if (this._preventConnectionsRefresh) {
                return;
            }

            this.trigger("dataBound");

            this._addConnections(this.connectionsDataSource.view());

            if (this.options.layout) {
                this.layout(this.options.layout);
            }
        },

        _removeShapes: function (items) {
            for (var i = 0; i < items.length; i++) {
                this.remove(this._dataMap[items[i].id], false);
                this._dataMap[items[i].id] = null;
            }
        },

        _syncShapes: function (items) {
            var inactiveItems = [],
                    i, y, item, inactiveShapeItem, isActive = false;

            for (y = 0; y < this._inactiveShapeItems.length; y++) {
                inactiveShapeItem = this._inactiveShapeItems[y];
                for (i = 0; i < items.length; i++) {
                    item = items[i];
                    if (inactiveShapeItem.uid === item.uid) {
                        this._addDataItem(item);
                        inactiveItems.push(inactiveShapeItem);
                        break;
                    }
                }
            }
            this._inactiveShapeItems = inactiveItems;
        },

        _updateShapes: function (items, field) {
            for (var i = 0; i < items.length; i++) {
                var dataItem = items[i];

                var shape = this._dataMap[dataItem.id];
                shape.updateOptionsFromModel(dataItem, field);
            }
        },

        _addShapes: function (dataItems) {
            for (var i = 0; i < dataItems.length; i++) {
                this._addDataItem(dataItems[i]);
            }
        },

        _refreshConnections: function (e) {
            if (e.action === "remove") {
                this._removeConnections(e.items);
            } else if (e.action === "add") {
                this._inactiveConnectionItems = this._inactiveConnectionItems.concat(e.items);
                this._addConnections(e.items);
            } else if (e.action === "sync") {
                this._syncConnections(e.items);
            } else if (e.action === "itemchange") {
                if (this._shouldRefresh) {
                    this._updateConnections(e.items);
                }
            } else {
                this.refreshConnections();
            }
        },

        _removeConnections: function (items) {
            for (var i = 0; i < items.length; i++) {
                this.remove(this._connectionsDataMap[items[i].uid], false);
                this._connectionsDataMap[items[i].uid] = null;
            }
        },

        _syncConnections: function (items) {
            var inactiveItems = [],
                    i, y, item, inactiveConnection, isActive = false;

            for (y = 0; y < this._inactiveConnectionItems.length; y++) {
                inactiveConnection = this._inactiveConnectionItems[y];
                for (i = 0; i < items.length; i++) {
                    item = items[i];
                    if (inactiveConnection.uid === item.uid) {
                        this._addConnectionDataItem(item);
                        inactiveItems.push(inactiveConnection);
                        break;
                    }
                }
            }
            this._inactiveConnectionDataMaps = inactiveItems;
        },

        _updateConnections: function (items) {
            for (var i = 0; i < items.length; i++) {
                var dataItem = items[i];

                var connection = this._connectionsDataMap[dataItem.uid];
                connection.updateOptionsFromModel(dataItem);

                var from = this._validateConnector(dataItem.from);
                if (from) {
                    connection.source(from);
                }

                var to = this._validateConnector(dataItem.to);
                if (to) {
                    connection.target(to);
                }
            }
        },

        _addConnections: function (connections) {
            var length = connections.length;

            for (var i = 0; i < length; i++) {
                var dataItem = connections[i];
                this._addConnectionDataItem(dataItem);
            }
        },

        _addConnectionDataItem: function (dataItem) {
            if (!this._connectionsDataMap[dataItem.uid]) {
                var from = this._validateConnector(dataItem.from);
                if (!defined(from) || from === null) {
                    from = new Point(dataItem.fromX, dataItem.fromY);
                }

                var to = this._validateConnector(dataItem.to);
                if (!defined(to) || to === null) {
                    to = new Point(dataItem.toX, dataItem.toY);
                }

                if (defined(from) && defined(to)) {
                    var options = deepExtend({}, this.options.connectionDefaults);
                    options.dataItem = dataItem;
                    var connection = new Connection(from, to, options);
                    connection.type(CASCADING);
                    this._connectionsDataMap[dataItem.uid] = connection;
                    this.addConnection(connection);
                }
            }
        },

        _validateConnector: function (value) {
            var connector;

            if (defined(value) && value !== null) {
                connector = this._dataMap[value];
            }

            return connector;
        },

        _treeDataSource: function () {
            var that = this,
                    options = that.options,
                    dataSource = options.dataSource;

            dataSource = isArray(dataSource) ? { data: dataSource} : dataSource;

            if (!dataSource.fields) {
                dataSource.fields = [
                        { field: "text" },
                        { field: "url" },
                        { field: "spriteCssClass" },
                        { field: "imageUrl" }
                    ];
            }
            if (that.dataSource && that._refreshHandler) {
                that._unbindDataSource();
            }

            that._refreshHandler = proxy(that._refreshSource, that);
            that._errorHandler = proxy(that._error, that);

            that.dataSource = HierarchicalDataSource.create(dataSource)
                    .bind(CHANGE, that._refreshHandler)
                    .bind(ERROR, that._errorHandler);
        },

        _unbindDataSource: function () {
            var that = this;

            that.dataSource.unbind(CHANGE, that._refreshHandler).unbind(ERROR, that._errorHandler);
        },
        _error: function () {
        },
        _adorn: function (adorner, isActive) {
            if (isActive !== undefined && adorner) {
                if (isActive) {
                    this._adorners.push(adorner);
                    this.adornerLayer.append(adorner.visual);
                }
                else {
                    Utils.remove(this._adorners, adorner);
                    this.adornerLayer.remove(adorner.visual);
                }
            }
        },

        _showConnectors: function (shape, value) {
            if (value) {
                this._connectorsAdorner.show(shape);
            } else {
                this._connectorsAdorner.destroy();
            }
        },

        _updateAdorners: function () {
            var adorners = this._adorners;

            for (var i = 0; i < adorners.length; i++) {
                var adorner = adorners[i];

                if (adorner.refreshBounds) {
                    adorner.refreshBounds();
                }
                adorner.refresh();
            }
        },

        _refresh: function () {
            for (var i = 0; i < this.connections.length; i++) {
                this.connections[i].refresh();
            }
        },

        _destroyToolBar: function () {
            if (this.singleToolBar) {
                this.singleToolBar.hide();
                this.singleToolBar.destroy();
                this.singleToolBar = null;
            }
        },

        _destroyGlobalToolBar: function () {
            if (this.toolBar) {
                this.toolBar.hide();
                this.toolBar.destroy();
                this.toolBar = null;
            }
        },

        exportDOMVisual: function () {
            var viewBox = this.canvas._viewBox;
            var scrollOffset = geom.transform().translate(
                    -viewBox.x, -viewBox.y
                );

            var viewRect = new geom.Rect(
                    [0, 0], [viewBox.width, viewBox.height]
                );
            var clipPath = draw.Path.fromRect(viewRect);

            var wrap = new draw.Group({
                transform: scrollOffset,
                clip: clipPath
            });

            var root = this.canvas.drawingElement.children[0];
            wrap.children.push(root);
            return wrap;
        },

        exportVisual: function () {
            var scale = geom.transform().scale(1 / this._zoom);
            var wrap = new draw.Group({
                transform: scale
            });

            var root = this.canvas.drawingElement.children[0];
            wrap.children.push(root);

            return wrap;
        }
    });

    dataviz.ExportMixin.extend(Diagram.fn, true);

    if (kendo.PDFMixin) {
        kendo.PDFMixin.extend(Diagram.fn);
    }

    function filterShapeDataItem(dataItem) {
        var result = {};

        dataItem = dataItem || {};

        if (defined(dataItem.text) && dataItem.text !== null) {
            result.text = dataItem.text;
        }

        if (defined(dataItem.x) && dataItem.x !== null) {
            result.x = dataItem.x;
        }

        if (defined(dataItem.y) && dataItem.y !== null) {
            result.y = dataItem.y;
        }

        if (defined(dataItem.width) && dataItem.width !== null) {
            result.width = dataItem.width;
        }

        if (defined(dataItem.height) && dataItem.height !== null) {
            result.height = dataItem.height;
        }

        if (defined(dataItem.type) && dataItem.type !== null) {
            result.type = dataItem.type;
        }

        return result;
    }

    function filterConnectionDataItem(dataItem) {
        var result = {};

        dataItem = dataItem || {};

        if (defined(dataItem.text) && dataItem.text !== null) {
            result.content = dataItem.text;
        }

        if (defined(dataItem.type) && dataItem.type !== null) {
            result.type = dataItem.type;
        }

        if (defined(dataItem.from) && dataItem.from !== null) {
            result.from = dataItem.from;
        }

        if (defined(dataItem.fromX) && dataItem.fromX !== null) {
            result.fromX = dataItem.fromX;
        }

        if (defined(dataItem.fromY) && dataItem.fromY !== null) {
            result.fromY = dataItem.fromY;
        }

        if (defined(dataItem.to) && dataItem.to !== null) {
            result.to = dataItem.to;
        }

        if (defined(dataItem.toX) && dataItem.toX !== null) {
            result.toX = dataItem.toX;
        }

        if (defined(dataItem.toY) && dataItem.toY !== null) {
            result.toY = dataItem.toY;
        }

        return result;
    }

    function isNumber(val) {
        return typeof val === "number" && !isNaN(val);
    }

    var DiagramToolBar = kendo.Observable.extend({
        init: function (diagram, options) {
            kendo.Observable.fn.init.call(this);
            this.diagram = diagram;
            this.options = deepExtend({}, this.options, options);
            this._tools = [];
            this.createToolBar();
            this.createTools();
            this.appendTools();

            if (this.options.modal) {
                this.createPopup();
            }

            this.bind(this.events, options);
        },

        events: ["click"],

        createPopup: function () {
            this.container = $("<div/>").append(this.element);
            this._popup = this.container.kendoPopup({}).getKendoPopup();
        },

        appendTools: function () {
            for (var i = 0; i < this._tools.length; i++) {
                var tool = this._tools[i];
                if (tool.buttons && tool.buttons.length || !defined(tool.buttons)) {
                    this._toolBar.add(tool);
                }
            }
        },

        createToolBar: function () {
            this.element = $("<div/>");
            this._toolBar = this.element
                    .kendoToolBar({
                        click: proxy(this.click, this),
                        resizable: false
                    }).getKendoToolBar();

            this.element.css("border", "none");
        },

        createTools: function () {
            for (var i = 0; i < this.options.tools.length; i++) {
                this.createTool(this.options.tools[i]);
            }
        },

        createTool: function (tool) {
            if (isPlainObject(tool)) {
                if (tool.name) {
                    this[tool.name + "Tool"](tool);
                } else if (tool.template) {
                    this._toolBar.add({
                        template: tool.template
                    });
                }
            } else {
                tool = this[tool + "Tool"]({});
                if (tool) {
                    tool.call(this);
                }
            }
        },

        showAt: function (point) {
            if (this._popup) {
                this._popup.open(point.x, point.y);
            }
        },

        hide: function () {
            if (this._popup) {
                this._popup.close();
            }
        },

        newGroup: function () {
            return {
                type: "buttonGroup",
                buttons: []
            };
        },

        editTool: function () {
            this._tools.push({
                spriteCssClass: "k-icon k-i-pencil",
                showText: "overflow",
                type: "button",
                text: "Edit",
                attributes: this._setAttributes({ action: "edit" })
            });
        },

        deleteTool: function () {
            this._tools.push({
                spriteCssClass: "k-icon k-i-close",
                showText: "overflow",
                type: "button",
                text: "Delete",
                attributes: this._setAttributes({ action: "delete" })
            });
        },

        rotateAnticlockwiseTool: function (options) {
            this._appendGroup("rotate");
            this._rotateGroup.buttons.push({
                spriteCssClass: "k-icon k-i-rotateccw",
                showText: "overflow",
                text: "RotateAnticlockwise",
                group: "rotate",
                attributes: this._setAttributes({ action: "rotateAnticlockwise", step: options.step })
            });
        },

        rotateClockwiseTool: function (options) {
            this._appendGroup("rotate");
            this._rotateGroup.buttons.push({
                spriteCssClass: "k-icon k-i-rotatecw",
                attributes: this._setAttributes({ action: "rotateClockwise", step: options.step }),
                showText: "overflow",
                text: "RotateClockwise",
                group: "rotate"
            });
        },

        createShapeTool: function () {
            this._appendGroup("create");
            this._createGroup.buttons.push({
                spriteCssClass: "k-icon k-i-shape",
                showText: "overflow",
                text: "CreateShape",
                group: "create",
                attributes: this._setAttributes({ action: "createShape" })
            });
        },

        createConnectionTool: function () {
            this._appendGroup("create");
            this._createGroup.buttons.push({
                spriteCssClass: "k-icon k-i-connector",
                showText: "overflow",
                text: "CreateConnection",
                group: "create",
                attributes: this._setAttributes({ action: "createConnection" })
            });
        },

        undoTool: function () {
            this._appendGroup("history");
            this._historyGroup.buttons.push({
                spriteCssClass: "k-icon k-i-undo",
                showText: "overflow",
                text: "Undo",
                group: "history",
                attributes: this._setAttributes({ action: "undo" })
            });
        },

        redoTool: function () {
            this._appendGroup("history");
            this._historyGroup.buttons.push({
                spriteCssClass: "k-icon k-i-redo",
                showText: "overflow",
                text: "Redo",
                group: "history",
                attributes: this._setAttributes({ action: "redo" })
            });
        },

        _appendGroup: function (name) {
            var prop = "_" + name + "Group";
            if (!this[prop]) {
                this[prop] = this.newGroup();
                this._tools.push(this[prop]);
            }
        },

        _setAttributes: function (attributes) {
            var attr = {};

            if (attributes.action) {
                attr[kendo.attr("action")] = attributes.action;
            }

            if (attributes.step) {
                attr[kendo.attr("step")] = attributes.step;
            }

            return attr;
        },

        _getAttributes: function (element) {
            var attr = {};

            var action = element.attr(kendo.attr("action"));
            if (action) {
                attr.action = action;
            }

            var step = element.attr(kendo.attr("step"));
            if (step) {
                attr.step = step;
            }

            return attr;
        },

        click: function (e) {
            var attributes = this._getAttributes($(e.target));
            var action = attributes.action;

            if (action) {
                this[action](attributes);
            }

            this.trigger("click", this.eventData(action));
        },

        eventData: function (action) {
            var element = this.selectedElements(),
                    shapes = [], connections = [];

            if (element instanceof Shape) {
                shapes.push(element);
            } else {
                connections.push(element);
            }

            return {
                shapes: shapes,
                connections: connections,
                action: action
            };
        },

        "delete": function () {
            this.diagram.remove(this.selectedElements(), true);
        },

        edit: function () {
            this.diagram.edit(this.selectedElements()[0]);
        },

        rotateClockwise: function (options) {
            var elements = this.selectedElements();
            var angle = parseFloat(options.step || 90);
            this._rotate(angle);
        },

        rotateAnticlockwise: function (options) {
            var elements = this.selectedElements();
            var angle = parseFloat(options.step || 90);
            this._rotate(-angle);
        },

        _rotate: function (angle) {
            var adorner = this.diagram._resizingAdorner;
            adorner.angle(adorner.angle() + angle);
            adorner.rotate();
        },

        selectedElements: function () {
            return this.diagram.select();
        },

        createShape: function () {
            this.diagram.createShape();
        },

        createConnection: function () {
            this.diagram.createConnection();
        },

        undo: function () {
            this.diagram.undo();
        },

        redo: function () {
            this.diagram.redo();
        },

        destroy: function () {
            this.diagram = null;
            this.element = null;
            this.options = null;

            if (this._toolBar) {
                this._toolBar.destroy();
            }

            if (this._popup) {
                this._popup.destroy();
            }
        }
    });

    var Editor = kendo.Observable.extend({
        init: function (element, options) {
            kendo.Observable.fn.init.call(this);

            this.options = extend(true, {}, this.options, options);
            this.element = element;
            this.model = this.options.model;
            this.fields = this._getFields();
            this._initContainer();
            this.createEditable();
        },

        options: {
            editors: {}
        },

        _initContainer: function () {
            this.wrapper = this.element;
        },

        createEditable: function () {
            var options = this.options;

            this.editable = new kendo.ui.Editable(this.wrapper, {
                fields: this.fields,
                target: options.target,
                clearContainer: false,
                model: this.model
            });
        },

        _isEditable: function (field) {
            return this.model.editable && this.model.editable(field);
        },

        _getFields: function () {
            var fields = [];
            var modelFields = this.model.fields;

            for (var field in modelFields) {
                var result = {};
                if (this._isEditable(field)) {
                    var editor = this.options.editors[field];
                    if (editor) {
                        result.editor = editor;
                    }
                    result.field = field;
                    fields.push(result);
                }
            }

            return fields;
        },

        end: function () {
            return this.editable.end();
        },

        destroy: function () {
            this.editable.destroy();
            this.editable.element.find("[" + kendo.attr("container-for") + "]").empty();
            this.model = this.wrapper = this.element = this.columns = this.editable = null;
        }
    });

    var PopupEditor = Editor.extend({
        init: function (element, options) {
            Editor.fn.init.call(this, element, options);
            this.bind(this.events, this.options);

            this.open();
        },

        events: ["update", "cancel"],

        options: {
            window: {
                modal: true,
                resizable: false,
                draggable: true,
                title: "Edit",
                visible: false
            }
        },

        _initContainer: function () {
            var that = this;
            this.wrapper = $('<div class="k-popup-edit-form"/>')
                    .attr(kendo.attr("uid"), this.model.uid);

            var formContent = "";

            if (this.options.template) {
                formContent += this._renderTemplate();
                this.fields = [];
            } else {
                formContent += this._renderFields();
            }

            formContent += this._renderButtons();

            this.wrapper.append(
                    $('<div class="k-edit-form-container"/>').append(formContent));

            this.window = new kendo.ui.Window(this.wrapper, this.options.window);
            this.window.bind("close", function (e) {
                //The bellow line is required due to: draggable window in IE, change event will be triggered while the window is closing
                if (e.userTriggered) {
                    e.sender.element.focus();
                    that._cancelClick();
                }
            });

            this._attachButtonEvents();
        },

        _renderTemplate: function () {
            var template = this.options.template;

            if (typeof template === "string") {
                template = window.unescape(template);
            }

            template = kendo.template(template)(this.model);

            return template;
        },

        _renderFields: function () {
            var form = "";
            for (var i = 0; i < this.fields.length; i++) {
                var field = this.fields[i];

                form += '<div class="k-edit-label"><label for="' + field.field + '">' + (field.field || "") + '</label></div>';

                if (this._isEditable(field.field)) {
                    form += '<div ' + kendo.attr("container-for") + '="' + field.field +
                        '" class="k-edit-field"></div>';
                }
            }

            return form;
        },

        _renderButtons: function () {
            var form = '<div class="k-edit-buttons k-state-default">';
            form += this._createButton("update");
            form += this._createButton("cancel");
            form += '</div>';
            return form;
        },

        _createButton: function (name) {
            return kendo.template(BUTTON_TEMPLATE)(defaultButtons[name]);
        },

        _attachButtonEvents: function () {
            this._cancelClickHandler = proxy(this._cancelClick, this);
            this.window.element.on(CLICK + NS, "a.k-diagram-cancel", this._cancelClickHandler);

            this._updateClickHandler = proxy(this._updateClick, this);
            this.window.element.on(CLICK + NS, "a.k-diagram-update", this._updateClickHandler);
        },

        _updateClick: function (e) {
            e.preventDefault();
            this.trigger("update");
        },

        _cancelClick: function (e) {
            e.preventDefault();
            this.trigger("cancel");
        },

        open: function () {
            this.window.center().open();
        },

        close: function () {
            this.window.bind("deactivate", proxy(this.destroy, this)).close();
        },

        destroy: function () {
            this.window.close().destroy();
            this.window.element.off(CLICK + NS, "a.k-diagram-cancel", this._cancelClickHandler);
            this.window.element.off(CLICK + NS, "a.k-diagram-update", this._updateClickHandler);
            this._cancelClickHandler = null;
            this._editUpdateClickHandler = null;
            this.window = null;
            Editor.fn.destroy.call(this);
        }
    });

    function connectionSelector(container, options) {
        var type = options.model.fields[options.field];
        var model = this.dataSource.reader.model;
        var textField = model.fn.fields.text ? "text" : model.idField;
        $("<input name='" + options.field + "' />")
                .appendTo(container).kendoDropDownList({
                    dataValueField: model.idField,
                    dataTextField: textField,
                    dataSource: this.dataSource.data().toJSON(),
                    optionLabel: " "
                });
    }

    dataviz.ui.plugin(Diagram);

    deepExtend(diagram, {
        Shape: Shape,
        Connection: Connection,
        Connector: Connector,
        DiagramToolBar: DiagramToolBar
    });
})(window.kendo.jQuery);





(function ($, undefined) {
    var math = Math,

        proxy = $.proxy,

        kendo = window.kendo,
        Class = kendo.Class,
        Widget = kendo.ui.Widget,
        template = kendo.template,
        deepExtend = kendo.deepExtend,
        HierarchicalDataSource = kendo.data.HierarchicalDataSource,
        getter = kendo.getter,

        dataviz = kendo.dataviz;

    var NS = ".kendoTreeMap",
        CHANGE = "change",
        DATA_BOUND = "dataBound",
        ITEM_CREATED = "itemCreated",
        MAX_VALUE = Number.MAX_VALUE,
        MIN_VALUE = -Number.MAX_VALUE,
        MOUSEOVER_NS = "mouseover" + NS,
        MOUSELEAVE_NS = "mouseleave" + NS,
        UNDEFINED = "undefined";

    var TreeMap = Widget.extend({
        init: function (element, options) {
            kendo.destroy(element);
            $(element).empty();

            Widget.fn.init.call(this, element, options);

            this._initTheme(this.options);

            this.element.addClass("k-widget k-treemap");

            this._setLayout();

            this._originalOptions = deepExtend({}, this.options);

            this._initDataSource();

            this._attachEvents();

            kendo.notify(this, dataviz.ui);
        },

        options: {
            name: "TreeMap",
            theme: "default",
            autoBind: true,
            textField: "text",
            valueField: "value",
            colorField: "color"
        },

        events: [DATA_BOUND, ITEM_CREATED],

        _initTheme: function (options) {
            var that = this,
                themes = dataviz.ui.themes || {},
                themeName = ((options || {}).theme || "").toLowerCase(),
                themeOptions = (themes[themeName] || {}).treeMap;

            that.options = deepExtend({}, themeOptions, options);
        },

        _attachEvents: function () {
            this.element
                .on(MOUSEOVER_NS, proxy(this._mouseover, this))
                .on(MOUSELEAVE_NS, proxy(this._mouseleave, this));
        },

        _setLayout: function () {
            if (this.options.type === "horizontal") {
                this._layout = new SliceAndDice(false);
                this._view = new SliceAndDiceView(this, this.options);
            } else if (this.options.type === "vertical") {
                this._layout = new SliceAndDice(true);
                this._view = new SliceAndDiceView(this, this.options);
            } else {
                this._layout = new Squarified();
                this._view = new SquarifiedView(this, this.options);
            }
        },

        _initDataSource: function () {
            var that = this,
                options = that.options,
                dataSource = options.dataSource;

            that._dataChangeHandler = proxy(that._onDataChange, that);

            that.dataSource = HierarchicalDataSource
                .create(dataSource)
                .bind(CHANGE, that._dataChangeHandler);

            if (dataSource) {
                if (that.options.autoBind) {
                    that.dataSource.fetch();
                }
            }
        },

        setDataSource: function (dataSource) {
            var that = this;
            that.dataSource.unbind(CHANGE, that._dataChangeHandler);
            that.dataSource = dataSource
                    .bind(CHANGE, that._dataChangeHandler);

            if (dataSource) {
                if (that.options.autoBind) {
                    that.dataSource.fetch();
                }
            }
        },

        _onDataChange: function (e) {
            var node = e.node;
            var items = e.items;
            var options = this.options;
            var item, i, colors;

            if (!node) {
                this.element.empty();
                item = this._wrapItem(items[0]);
                this._layout.createRoot(
                    item,
                    this.element.outerWidth(),
                    this.element.outerHeight(),
                    this.options.type === "vertical"
                );
                this._view.createRoot(item);
                // Reference of the root
                this._root = item;
            } else {
                if (items.length) {
                    var root = this._getByUid(node.uid);
                    root.children = [];

                    if (!defined(root.minColor) && !defined(root.maxColor)) {
                        colors = options.colors || [];
                    } else {
                        colors = colorsByLength(root.minColor, root.maxColor, items.length) || [];
                    }

                    for (i = 0; i < items.length; i++) {
                        item = items[i];
                        root.children.push(this._wrapItem(item));
                    }

                    var htmlSize = this._view.htmlSize(root);
                    this._layout.compute(root.children, root.coord, htmlSize);

                    for (i = 0; i < root.children.length; i++) {
                        item = root.children[i];
                        if (!defined(item.color)) {
                            if (typeof (colors[0]) === "string") {
                                item.color = colors[i % colors.length];
                            } else {
                                var currentColors = colors[i % colors.length];
                                if (currentColors) {
                                    item.color = currentColors[0];
                                    item.minColor = currentColors[0];
                                    item.maxColor = currentColors[1];
                                }
                            }
                        }
                    }

                    this._view.render(root);
                }
            }

            for (i = 0; i < items.length; i++) {
                items[i].load();
            }

            if (node) {
                this.trigger(DATA_BOUND, {
                    node: node
                });
            }
        },

        _contentSize: function (root) {
            this.view.renderHeight(root);
        },

        _wrapItem: function (item) {
            var wrap = {};

            if (defined(this.options.valueField)) {
                wrap.value = getField(this.options.valueField, item);
            }

            if (defined(this.options.colorField)) {
                wrap.color = getField(this.options.colorField, item);
            }

            if (defined(this.options.textField)) {
                wrap.text = getField(this.options.textField, item);
            }

            wrap.level = item.level();

            wrap.dataItem = item;

            return wrap;
        },

        _getByUid: function (uid) {
            var items = [this._root];
            var item;

            while (items.length) {
                item = items.pop();
                if (item.dataItem.uid === uid) {
                    return item;
                }

                if (item.children) {
                    items = items.concat(item.children);
                }
            }
        },

        dataItem: function (node) {
            var uid = $(node).attr(kendo.attr("uid")),
                dataSource = this.dataSource;

            return dataSource && dataSource.getByUid(uid);
        },

        findByUid: function (uid) {
            return this.element.find(".k-treemap-tile[" + kendo.attr("uid") + "='" + uid + "']");
        },

        _mouseover: function (e) {
            var target = $(e.target);
            if (target.hasClass("k-leaf")) {
                this._removeActiveState();
                target
                    .removeClass("k-state-hover")
                    .addClass("k-state-hover");
            }
        },

        _removeActiveState: function () {
            this.element
                .find(".k-state-hover")
                .removeClass("k-state-hover");
        },

        _mouseleave: function () {
            this._removeActiveState();
        },

        destroy: function () {
            Widget.fn.destroy.call(this);
            this.element.off(NS);

            if (this.dataSource) {
                this.dataSource.unbind(CHANGE, this._dataChangeHandler);
            }

            this._root = null;

            kendo.destroy(this.element);
        },

        items: function () {
            return $();
        },

        getSize: function () {
            return kendo.dimensions(this.element);
        },

        _resize: function () {
            this.dataSource.fetch();
        },

        setOptions: function (options) {
            var dataSource = options.dataSource;

            options.dataSource = undefined;
            this._originalOptions = deepExtend(this._originalOptions, options);
            this.options = deepExtend({}, this._originalOptions);
            this._setLayout();
            this._initTheme(this.options);

            Widget.fn._setEvents.call(this, options);

            if (dataSource) {
                this.setDataSource(HierarchicalDataSource.create(dataSource));
            }

            if (this.options.autoBind) {
                this.dataSource.fetch();
            }
        }
    });

    var Squarified = Class.extend({
        createRoot: function (root, width, height) {
            root.coord = {
                width: width,
                height: height,
                top: 0,
                left: 0
            };
        },

        leaf: function (tree) {
            return !tree.children;
        },

        layoutChildren: function (items, coord) {
            var parentArea = coord.width * coord.height;
            var totalArea = 0,
                itemsArea = [],
                i;

            for (i = 0; i < items.length; i++) {
                itemsArea[i] = parseFloat(items[i].value);
                totalArea += itemsArea[i];
            }

            for (i = 0; i < itemsArea.length; i++) {
                items[i].area = parentArea * itemsArea[i] / totalArea;
            }

            var minimumSideValue = this.layoutHorizontal() ? coord.height : coord.width;

            items = new kendo.data.Query(items)._sortForGrouping("value", "desc");
            var firstElement = [items[0]];
            var tail = items.slice(1);
            this.squarify(tail, firstElement, minimumSideValue, coord);
        },

        squarify: function (tail, initElement, width, coord) {
            this.computeDim(tail, initElement, width, coord);
        },

        computeDim: function (tail, initElement, width, coord) {
            if (tail.length + initElement.length == 1) {
                var element = tail.length == 1 ? tail : initElement;
                this.layoutLast(element, width, coord);
                return;
            }

            if (tail.length >= 2 && initElement.length === 0) {
                initElement = [tail[0]];
                tail = tail.slice(1);
            }

            if (tail.length === 0) {
                if (initElement.length > 0) {
                    this.layoutRow(initElement, width, coord);
                }
                return;
            }

            var firstElement = tail[0];

            if (this.worstAspectRatio(initElement, width) >= this.worstAspectRatio([firstElement].concat(initElement), width)) {
                this.computeDim(tail.slice(1), initElement.concat([firstElement]), width, coord);
            } else {
                var newCoords = this.layoutRow(initElement, width, coord);
                this.computeDim(tail, [], newCoords.dim, newCoords);
            }
        },

        layoutLast: function (items, w, coord) {
            items[0].coord = coord;
        },

        layoutRow: function (items, width, coord) {
            if (this.layoutHorizontal()) {
                return this.layoutV(items, width, coord);
            } else {
                return this.layoutH(items, width, coord);
            }
        },

        orientation: "h",

        layoutVertical: function () {
            return this.orientation === "v";
        },

        layoutHorizontal: function () {
            return this.orientation === "h";
        },

        layoutChange: function () {
            this.orientation = this.layoutVertical() ? "h" : "v";
        },

        worstAspectRatio: function (items, width) {
            if (!items || items.length === 0) {
                return MAX_VALUE;
            }

            var areaSum = 0,
                maxArea = 0,
                minArea = MAX_VALUE;

            for (var i = 0; i < items.length; i++) {
                var area = items[i].area;
                areaSum += area;
                minArea = (minArea < area) ? minArea : area;
                maxArea = (maxArea > area) ? maxArea : area;
            }

            return math.max(
                (width * width * maxArea) / (areaSum * areaSum),
                (areaSum * areaSum) / (width * width * minArea)
            );
        },

        compute: function (children, rootCoord, htmlSize) {
            if (!(rootCoord.width >= rootCoord.height && this.layoutHorizontal())) {
                this.layoutChange();
            }

            if (children && children.length > 0) {
                var newRootCoord = {
                    width: rootCoord.width,
                    height: rootCoord.height - htmlSize.text,
                    top: 0,
                    left: 0
                };

                this.layoutChildren(children, newRootCoord);
            }
        },

        layoutV: function (items, width, coord) {
            var totalArea = this._totalArea(items),
                top = 0;

            width = round(totalArea / width);

            for (var i = 0; i < items.length; i++) {
                var height = round(items[i].area / width);
                items[i].coord = {
                    height: height,
                    width: width,
                    top: coord.top + top,
                    left: coord.left
                };

                top += height;
            }

            var ans = {
                height: coord.height,
                width: coord.width - width,
                top: coord.top,
                left: coord.left + width
            };

            ans.dim = math.min(ans.width, ans.height);

            if (ans.dim != ans.height) {
                this.layoutChange();
            }

            return ans;
        },

        layoutH: function (items, width, coord) {
            var totalArea = this._totalArea(items);

            var height = round(totalArea / width),
                top = coord.top,
                left = 0;

            for (var i = 0; i < items.length; i++) {
                items[i].coord = {
                    height: height,
                    width: round(items[i].area / height),
                    top: top,
                    left: coord.left + left
                };
                left += items[i].coord.width;
            }

            var ans = {
                height: coord.height - height,
                width: coord.width,
                top: coord.top + height,
                left: coord.left
            };

            ans.dim = math.min(ans.width, ans.height);

            if (ans.dim != ans.width) {
                this.layoutChange();
            }

            return ans;
        },

        _totalArea: function (items) {
            var total = 0;

            for (var i = 0; i < items.length; i++) {
                total += items[i].area;
            }

            return total;
        }
    });

    var SquarifiedView = Class.extend({
        init: function (treeMap, options) {
            this.options = deepExtend({}, this.options, options);
            this.treeMap = treeMap;
            this.element = $(treeMap.element);

            this.offset = 0;
        },

        htmlSize: function (root) {
            var rootElement = this._getByUid(root.dataItem.uid);
            var htmlSize = {
                text: 0
            };

            if (root.children) {
                this._clean(rootElement);

                var text = this._getText(root);
                if (text) {
                    var title = this._createTitle(root);
                    rootElement.append(title);

                    htmlSize.text = title.height();
                }

                rootElement.append(this._createWrap());

                this.offset = (rootElement.outerWidth() - rootElement.innerWidth()) / 2;
            }

            return htmlSize;
        },

        _getByUid: function (uid) {
            return this.element.find(".k-treemap-tile[" + kendo.attr("uid") + "='" + uid + "']");
        },

        render: function (root) {
            var rootElement = this._getByUid(root.dataItem.uid);
            var children = root.children;
            if (children) {
                var rootWrap = rootElement.find(".k-treemap-wrap");

                for (var i = 0; i < children.length; i++) {
                    var leaf = children[i];
                    var htmlElement = this._createLeaf(leaf);
                    rootWrap.append(htmlElement);
                    this.treeMap.trigger(ITEM_CREATED, {
                        element: htmlElement
                    });
                }
            }
        },

        createRoot: function (root) {
            var htmlElement = this._createLeaf(root);
            this.element.append(htmlElement);

            this.treeMap.trigger(ITEM_CREATED, {
                element: htmlElement
            });
        },

        _clean: function (root) {
            root.css("background-color", "");
            root.removeClass("k-leaf");
            root.removeClass("k-inverse");
            root.empty();
        },

        _createLeaf: function (item) {
            return this._createTile(item)
                    .css("background-color", item.color)
                    .addClass("k-leaf")
                    .toggleClass(
                        "k-inverse",
                        this._tileColorBrightness(item) > 180
                    )
                    .append($("<div></div>")
                    .html(this._getText(item)));
        },

        _createTile: function (item) {
            var newCoord = {
                width: item.coord.width,
                height: item.coord.height,
                left: item.coord.left,
                top: item.coord.top
            };

            if (newCoord.left && this.offset) {
                newCoord.width += this.offset * 2;
            } else {
                newCoord.width += this.offset;
            }

            if (newCoord.top) {
                newCoord.height += this.offset * 2;
            } else {
                newCoord.height += this.offset;
            }

            var tile = $("<div class='k-treemap-tile'></div>")
                .css({
                    width: newCoord.width,
                    height: newCoord.height,
                    left: newCoord.left,
                    top: newCoord.top
                });

            if (defined(item.dataItem) && defined(item.dataItem.uid)) {
                tile.attr(kendo.attr("uid"), item.dataItem.uid);
            }

            return tile;
        },

        _getText: function (item) {
            var text = item.text;

            if (this.options.template) {
                text = this._renderTemplate(item);
            }

            return text;
        },

        _renderTemplate: function (item) {
            var titleTemplate = template(this.options.template);
            return titleTemplate({
                dataItem: item.dataItem,
                text: item.text
            });
        },

        _createTitle: function (item) {
            return $("<div class='k-treemap-title'></div>")
                    .append($("<div></div>").html(this._getText(item)));
        },

        _createWrap: function () {
            return $("<div class='k-treemap-wrap'></div>");
        },

        _tileColorBrightness: function (item) {
            return colorBrightness(item.color);
        }
    });

    var SliceAndDice = Class.extend({
        createRoot: function (root, width, height, vertical) {
            root.coord = {
                width: width,
                height: height,
                top: 0,
                left: 0
            };
            root.vertical = vertical;
        },

        init: function (vertical) {
            this.vertical = vertical;
            this.quotient = vertical ? 1 : 0;
        },

        compute: function (children, rootCoord, htmlSize) {
            if (children.length > 0) {
                var width = rootCoord.width;
                var height = rootCoord.height;

                if (this.vertical) {
                    height -= htmlSize.text;
                } else {
                    width -= htmlSize.text;
                }

                var newRootCoord = {
                    width: width,
                    height: height,
                    top: 0,
                    left: 0
                };

                this.layoutChildren(children, newRootCoord);
            }
        },

        layoutChildren: function (items, coord) {
            var parentArea = coord.width * coord.height;
            var totalArea = 0;
            var itemsArea = [];
            var i;

            for (i = 0; i < items.length; i++) {
                var item = items[i];
                itemsArea[i] = parseFloat(items[i].value);
                totalArea += itemsArea[i];
                item.vertical = this.vertical;
            }

            for (i = 0; i < itemsArea.length; i++) {
                items[i].area = parentArea * itemsArea[i] / totalArea;
            }

            items = new kendo.data.Query(items)._sortForGrouping("value", "desc");

            this.sliceAndDice(items, coord);
        },

        sliceAndDice: function (items, coord) {
            var totalArea = this._totalArea(items);
            if (items[0].level % 2 === this.quotient) {
                this.layoutHorizontal(items, coord, totalArea);
            } else {
                this.layoutVertical(items, coord, totalArea);
            }
        },

        layoutHorizontal: function (items, coord, totalArea) {
            var left = 0;

            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                var width = item.area / (totalArea / coord.width);
                item.coord = {
                    height: coord.height,
                    width: width,
                    top: coord.top,
                    left: coord.left + left
                };

                left += width;
            }
        },

        layoutVertical: function (items, coord, totalArea) {
            var top = 0;

            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                var height = item.area / (totalArea / coord.height);
                item.coord = {
                    height: height,
                    width: coord.width,
                    top: coord.top + top,
                    left: coord.left
                };

                top += height;
            }
        },

        _totalArea: function (items) {
            var total = 0;

            for (var i = 0; i < items.length; i++) {
                total += items[i].area;
            }

            return total;
        }
    });

    var SliceAndDiceView = SquarifiedView.extend({
        htmlSize: function (root) {
            var rootElement = this._getByUid(root.dataItem.uid);
            var htmlSize = {
                text: 0,
                offset: 0
            };

            if (root.children) {
                this._clean(rootElement);

                var text = this._getText(root);
                if (text) {
                    var title = this._createTitle(root);
                    rootElement.append(title);

                    if (root.vertical) {
                        htmlSize.text = title.height();
                    } else {
                        htmlSize.text = title.width();
                    }
                }

                rootElement.append(this._createWrap());

                this.offset = (rootElement.outerWidth() - rootElement.innerWidth()) / 2;
            }

            return htmlSize;
        },

        _createTitle: function (item) {
            var title;
            if (item.vertical) {
                title = $("<div class='k-treemap-title'></div>");
            } else {
                title = $("<div class='k-treemap-title-vertical'></div>");
            }

            return title.append($("<div></div>").html(this._getText(item)));
        }
    });

    function valueOrDefault(value, defaultValue) {
        return defined(value) ? value : defaultValue;
    }

    function getField(field, row) {
        if (row === null) {
            return row;
        }

        var get = getter(field, true);
        return get(row);
    }

    function defined(value) {
        return typeof value !== UNDEFINED;
    }

    function colorsByLength(min, max, length) {
        var minRGBtoDecimal = rgbToDecimal(min);
        var maxRGBtoDecimal = rgbToDecimal(max);
        var isDarker = colorBrightness(min) - colorBrightness(max) < 0;
        var colors = [];

        colors.push(min);

        for (var i = 0; i < length; i++) {
            var rgbColor = {
                r: colorByIndex(minRGBtoDecimal.r, maxRGBtoDecimal.r, i, length, isDarker),
                g: colorByIndex(minRGBtoDecimal.g, maxRGBtoDecimal.g, i, length, isDarker),
                b: colorByIndex(minRGBtoDecimal.b, maxRGBtoDecimal.b, i, length, isDarker)
            };
            colors.push(buildColorFromRGB(rgbColor));
        }

        colors.push(max);

        return colors;
    }

    function colorByIndex(min, max, index, length, isDarker) {
        var minColor = math.min(math.abs(min), math.abs(max));
        var maxColor = math.max(math.abs(min), math.abs(max));
        var step = (maxColor - minColor) / (length + 1);
        var currentStep = step * (index + 1);
        var color;

        if (isDarker) {
            color = minColor + currentStep;
        } else {
            color = maxColor - currentStep;
        }

        return color;
    }

    function buildColorFromRGB(color) {
        return "#" + decimalToRgb(color.r) + decimalToRgb(color.g) + decimalToRgb(color.b);
    }

    function rgbToDecimal(color) {
        color = color.replace("#", "");
        var rgbColor = colorToRGB(color);

        return {
            r: rgbToHex(rgbColor.r),
            g: rgbToHex(rgbColor.g),
            b: rgbToHex(rgbColor.b)
        };
    }

    function decimalToRgb(number) {
        var result = math.round(number).toString(16).toUpperCase();

        if (result.length === 1) {
            result = "0" + result;
        }

        return result;
    }

    function colorToRGB(color) {
        var colorLength = color.length;
        var rgbColor = {};
        if (colorLength === 3) {
            rgbColor.r = color[0];
            rgbColor.g = color[1];
            rgbColor.b = color[2];
        } else {
            rgbColor.r = color.substring(0, 2);
            rgbColor.g = color.substring(2, 4);
            rgbColor.b = color.substring(4, 6);
        }

        return rgbColor;
    }

    function rgbToHex(rgb) {
        return parseInt(rgb.toString(16), 16);
    }

    function colorBrightness(color) {
        var brightness = 0;
        if (color) {
            color = rgbToDecimal(color);
            brightness = math.sqrt(0.241 * color.r * color.r + 0.691 * color.g * color.g + 0.068 * color.b * color.b);
        }

        return brightness;
    }

    function round(value) {
        var power = math.pow(10, 4);
        return math.round(value * power) / power;
    }

    dataviz.ui.plugin(TreeMap);

})(window.kendo.jQuery);







(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.mobile.ui,
        Shim = ui.Shim,
        Widget = ui.Widget,
        BEFORE_OPEN = "beforeOpen",
        OPEN = "open",
        CLOSE = "close",
        INIT = "init",
        WRAP = '<div class="km-modalview-wrapper" />';

    var ModalView = ui.View.extend({
        init: function (element, options) {
            var that = this, width, height;

            Widget.fn.init.call(that, element, options);

            that._id();
            that._wrap();
            that._shim();

            if (!this.options.$angular) {
                that._layout();
                that._scroller();
                that._model();
            }

            that.element.css("display", "");

            that.trigger(INIT);
        },

        events: [
            INIT,
            BEFORE_OPEN,
            OPEN,
            CLOSE
        ],

        options: {
            name: "ModalView",
            modal: true,
            width: null,
            height: null
        },

        destroy: function () {
            Widget.fn.destroy.call(this);
            this.shim.destroy();
        },

        open: function (target) {
            var that = this;
            that.target = $(target);
            that.shim.show();

            that._invokeNgController();

            // necessary for the mobile view interface
            that.trigger("show", { view: that });
        },

        // Interface implementation, called from the pane click handlers
        openFor: function (target) {
            if (!this.trigger(BEFORE_OPEN, { target: target })) {
                this.open(target);
                this.trigger(OPEN, { target: target });
            }
        },

        close: function () {
            if (this.element.is(":visible") && !this.trigger(CLOSE)) {
                this.shim.hide();
            }
        },

        _wrap: function () {
            var that = this,
                element = that.element,
                options = that.options,
                width, height;

            width = element[0].style.width || "auto";
            height = element[0].style.height || "auto";

            element.addClass("km-modalview").wrap(WRAP);

            that.wrapper = element.parent().css({
                width: options.width || width || 300,
                height: options.height || height || 300
            }).addClass(height == "auto" ? " km-auto-height" : "");

            element.css({ width: "", height: "" });
        },

        _shim: function () {
            var that = this;

            that.shim = new Shim(that.wrapper, {
                modal: that.options.modal,
                position: "center center",
                align: "center center",
                effect: "fade:in",
                className: "km-modalview-root",
                hide: function (e) {
                    if (that.trigger(CLOSE)) {
                        e.preventDefault();
                    }
                }
            });
        }
    });

    ui.plugin(ModalView);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        mobile = kendo.mobile,
        os = kendo.support.mobileOS,
        Transition = kendo.effects.Transition,
        roleSelector = kendo.roleSelector,
        AXIS = "x",
        ui = mobile.ui,
        SWIPE_TO_OPEN = !(os.ios && os.majorVersion == 7 && !os.appMode),
        BEFORE_SHOW = "beforeShow",
        INIT = "init",
        SHOW = "show",
        HIDE = "hide",
        AFTER_HIDE = "afterHide",
        NULL_VIEW = { enable: $.noop };

    var Drawer = ui.View.extend({
        init: function (element, options) {
            // move the drawer to the top, in order to hide it
            $(element).parent().prepend(element);

            mobile.ui.Widget.fn.init.call(this, element, options);

            if (!this.options.$angular) {
                this._layout();
                this._scroller();
            }

            this._model();

            var pane = this.element.closest(roleSelector("pane")).data("kendoMobilePane"),
                userEvents;

            if (pane) {
                this.pane = pane;
                this.pane.bind("viewShow", function (e) {
                    drawer._viewShow(e);
                });

                this.pane.bind("sameViewRequested", function () {
                    drawer.hide();
                });

                userEvents = this.userEvents = new kendo.UserEvents(pane.element, {
                    filter: roleSelector("view splitview"),
                    allowSelection: true
                });

            } else {
                this.currentView = NULL_VIEW;
                var container = $(this.options.container);

                if (!container) {
                    throw new Error("The drawer needs a container configuration option set.");
                }

                userEvents = this.userEvents = new kendo.UserEvents(container, { allowSelection: true });
                this._attachTransition(container);
            }

            var drawer = this;

            var hide = function (e) {
                if (drawer.visible) {
                    drawer.hide();
                    e.preventDefault();
                }
            };

            if (this.options.swipeToOpen && SWIPE_TO_OPEN) {
                userEvents.bind("press", function (e) { drawer.transition.cancel(); });
                userEvents.bind("start", function (e) { drawer._start(e); });
                userEvents.bind("move", function (e) { drawer._update(e); });
                userEvents.bind("end", function (e) { drawer._end(e); });
                userEvents.bind("tap", hide);
            } else {
                userEvents.bind("press", hide);
            }

            this.leftPositioned = this.options.position === "left";

            this.visible = false;

            this.element.hide().addClass("km-drawer").addClass(this.leftPositioned ? "km-left-drawer" : "km-right-drawer");
            this.trigger(INIT);
        },

        options: {
            name: "Drawer",
            position: "left",
            views: [],
            swipeToOpenViews: [],
            swipeToOpen: true,
            title: "",
            container: null
        },

        events: [
            BEFORE_SHOW,
            HIDE,
            AFTER_HIDE,
            INIT,
            SHOW
        ],

        show: function () {
            if (this._activate()) {
                this._show();
            }
        },

        hide: function () {
            if (!this.currentView) {
                return;
            }

            this.currentView.enable();

            Drawer.current = null;
            this._moveViewTo(0);
            this.trigger(HIDE, { view: this });
        },

        // Alias in order to support popover/modalview etc. interface
        openFor: function () {
            if (this.visible) {
                this.hide();
            } else {
                this.show();
            }
        },

        destroy: function () {
            ui.View.fn.destroy.call(this);
            this.userEvents.destroy();
        },

        _activate: function () {
            if (this.visible) {
                return true;
            }

            var visibleOnCurrentView = this._currentViewIncludedIn(this.options.views);

            if (!visibleOnCurrentView || this.trigger(BEFORE_SHOW, { view: this })) {
                return false;
            }

            this._setAsCurrent();
            this.element.show();

            this.trigger(SHOW, { view: this });
            this._invokeNgController();
            return true;
        },

        _currentViewIncludedIn: function (views) {
            if (!this.pane || !views.length) {
                return true;
            }

            var view = this.pane.view();
            return $.inArray(view.id.replace('#', ''), views) > -1 || $.inArray(view.element.attr("id"), views) > -1;
        },

        _show: function () {
            this.currentView.enable(false);

            this.visible = true;
            var offset = this.element.width();

            if (!this.leftPositioned) {
                offset = -offset;
            }

            this._moveViewTo(offset);
        },

        _setAsCurrent: function () {
            if (Drawer.last !== this) {
                if (Drawer.last) {
                    Drawer.last.element.hide();
                }
                this.element.show();
            }

            Drawer.last = this;
            Drawer.current = this;
        },

        _moveViewTo: function (offset) {
            this.userEvents.cancel();
            this.transition.moveTo({ location: offset, duration: 400, ease: Transition.easeOutExpo });
        },

        _viewShow: function (e) {
            if (this.currentView) {
                this.currentView.enable();
            }

            if (this.currentView === e.view) {
                this.hide();
                return;
            }

            this.currentView = e.view;
            this._attachTransition(e.view.element);
        },

        _attachTransition: function (element) {
            var that = this,
                movable = this.movable,
                currentOffset = movable && movable.x;


            if (this.transition) {
                this.transition.cancel();
                this.movable.moveAxis("x", 0);
            }

            movable = this.movable = new kendo.ui.Movable(element);

            this.transition = new Transition({
                axis: AXIS,
                movable: this.movable,
                onEnd: function () {
                    if (movable[AXIS] === 0) {
                        element[0].style.cssText = "";
                        that.element.hide();
                        that.trigger(AFTER_HIDE);
                        that.visible = false;
                    }
                }
            });

            if (currentOffset) {
                element.addClass("k-fx-hidden");
                kendo.animationFrame(function () {
                    element.removeClass("k-fx-hidden");
                    that.movable.moveAxis(AXIS, currentOffset);
                    that.hide();
                });
            }
        },

        _start: function (e) {
            var userEvents = e.sender;

            // ignore non-horizontal swipes
            if (Math.abs(e.x.velocity) < Math.abs(e.y.velocity) || kendo.triggeredByInput(e.event) || !this._currentViewIncludedIn(this.options.swipeToOpenViews)) {
                userEvents.cancel();
                return;
            }

            var leftPositioned = this.leftPositioned,
                visible = this.visible,
                canMoveLeft = leftPositioned && visible || !leftPositioned && !Drawer.current,
                canMoveRight = !leftPositioned && visible || leftPositioned && !Drawer.current,
                leftSwipe = e.x.velocity < 0;

            if ((canMoveLeft && leftSwipe) || (canMoveRight && !leftSwipe)) {
                if (this._activate()) {
                    userEvents.capture();
                    return;
                }
            }

            userEvents.cancel();
        },

        _update: function (e) {
            var movable = this.movable,
                newPosition = movable.x + e.x.delta,
                limitedPosition;

            if (this.leftPositioned) {
                limitedPosition = Math.min(Math.max(0, newPosition), this.element.width());
            } else {
                limitedPosition = Math.max(Math.min(0, newPosition), -this.element.width());
            }

            this.movable.moveAxis(AXIS, limitedPosition);
            e.event.preventDefault();
            e.event.stopPropagation();
        },

        _end: function (e) {
            var velocity = e.x.velocity,
                pastHalf = Math.abs(this.movable.x) > this.element.width() / 2,
                velocityThreshold = 0.8,
                shouldShow;

            if (this.leftPositioned) {
                shouldShow = velocity > -velocityThreshold && (velocity > velocityThreshold || pastHalf);
            } else {
                shouldShow = velocity < velocityThreshold && (velocity < -velocityThreshold || pastHalf);
            }

            if (shouldShow) {
                this._show();
            } else {
                this.hide();
            }
        }
    });

    ui.plugin(Drawer);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.mobile.ui,
        Widget = ui.Widget,
        EXPANED_PANE_SHIM = "<div class='km-expanded-pane-shim' />",
        View = ui.View;

    var SplitView = View.extend({
        init: function (element, options) {
            var that = this,
            pane, modalViews;

            Widget.fn.init.call(that, element, options);
            element = that.element;

            $.extend(that, options);

            that._id();

            if (!that.options.$angular) {
                that._layout();
                that._overlay();
            } else {
                that._overlay();
            }

            that._style();

            modalViews = element.children(that._locate("modalview"));

            if (!that.options.$angular) {
                kendo.mobile.init(modalViews);
            } else {
                modalViews.each(function (idx, element) {
                    kendo.compileMobileDirective($(element));
                });
            }

            that.panes = [];
            that._paramsHistory = [];

            if (!that.options.$angular) {
                that.content.children(kendo.roleSelector("pane")).each(function () {
                    pane = kendo.initWidget(this, {}, ui.roles);
                    that.panes.push(pane);
                });
            } else {
                that.element.children(kendo.directiveSelector("pane")).each(function () {
                    pane = kendo.compileMobileDirective($(this));
                    that.panes.push(pane);
                });
            }

            that.expandedPaneShim = $(EXPANED_PANE_SHIM).appendTo(that.element);

            that._shimUserEvents = new kendo.UserEvents(that.expandedPaneShim, {
                tap: function () {
                    that.collapsePanes();
                }
            });
        },

        _locate: function (selectors) {
            return this.options.$angular ? kendo.directiveSelector(selectors) : kendo.roleSelector(selectors);
        },

        options: {
            name: "SplitView",
            style: "horizontal"
        },

        expandPanes: function () {
            this.element.addClass("km-expanded-splitview");
        },

        collapsePanes: function () {
            this.element.removeClass("km-expanded-splitview");
        },

        // Implement view interface
        _layout: function () {
            var that = this,
                element = that.element;

            that.transition = kendo.attrValue(element, "transition");
            kendo.mobile.ui.View.prototype._layout.call(this);
            kendo.mobile.init(this.header.add(this.footer));
            that.element.addClass("km-splitview");
            that.content.addClass("km-split-content");
        },

        _style: function () {
            var style = this.options.style,
                element = this.element,
                styles;

            if (style) {
                styles = style.split(" ");
                $.each(styles, function () {
                    element.addClass("km-split-" + this);
                });
            }
        },

        showStart: function () {
            var that = this;
            that.element.css("display", "");

            if (!that.inited) {
                that.inited = true;
                $.each(that.panes, function () {
                    if (this.options.initial) {
                        this.navigateToInitial();
                    } else {
                        this.navigate("");
                    }
                });
                that.trigger("init", { view: that });
            }

            that.trigger("show", { view: that });
        }
    });

    ui.plugin(SplitView);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        mobile = kendo.mobile,
        support = kendo.support,
        Widget = mobile.ui.Widget,
        Pane = mobile.ui.Pane,

        DEFAULT_OS = "ios7",
        OS = support.mobileOS,
        BERRYPHONEGAP = OS.device == "blackberry" && OS.flatVersion >= 600 && OS.flatVersion < 1000 && OS.appMode,
        VERTICAL = "km-vertical",
        CHROME = OS.browser === "chrome",
        BROKEN_WEBVIEW_RESIZE = OS.ios && OS.flatVersion >= 700 && OS.flatVersion < 800 && (OS.appMode || CHROME),
        INITIALLY_HORIZONTAL = (Math.abs(window.orientation) / 90 == 1),
        HORIZONTAL = "km-horizontal",

        MOBILE_PLATFORMS = {
            ios7: { ios: true, browser: "default", device: "iphone", flatVersion: "700", majorVersion: "7", minorVersion: "0.0", name: "ios", tablet: false },
            ios: { ios: true, browser: "default", device: "iphone", flatVersion: "612", majorVersion: "6", minorVersion: "1.2", name: "ios", tablet: false },
            android: { android: true, browser: "default", device: "android", flatVersion: "442", majorVersion: "4", minorVersion: "4.2", name: "android", tablet: false },
            blackberry: { blackberry: true, browser: "default", device: "blackberry", flatVersion: "710", majorVersion: "7", minorVersion: "1.0", name: "blackberry", tablet: false },
            meego: { meego: true, browser: "default", device: "meego", flatVersion: "850", majorVersion: "8", minorVersion: "5.0", name: "meego", tablet: false },
            wp: { wp: true, browser: "default", device: "wp", flatVersion: "800", majorVersion: "8", minorVersion: "0.0", name: "wp", tablet: false }
        },

        viewportTemplate = kendo.template('<meta content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no#=data.height#" name="viewport" />', { usedWithBlock: false }),
        systemMeta = kendo.template('<meta name="apple-mobile-web-app-capable" content="#= data.webAppCapable === false ? \'no\' : \'yes\' #" /> ' +
                     '<meta name="apple-mobile-web-app-status-bar-style" content="#=data.statusBarStyle#" /> ' +
                     '<meta name="msapplication-tap-highlight" content="no" /> ', { usedWithBlock: false }),
        clipTemplate = kendo.template('<style>.km-view { clip: rect(0 #= data.width #px #= data.height #px 0); }</style>', { usedWithBlock: false }),
        ENABLE_CLIP = OS.android && OS.browser != "chrome" || OS.blackberry,
        viewportMeta = viewportTemplate({ height: "" }),

        iconMeta = kendo.template('<link rel="apple-touch-icon' + (OS.android ? '-precomposed' : '') + '" # if(data.size) { # sizes="#=data.size#" #}# href="#=data.icon#" />', { usedWithBlock: false }),

        HIDEBAR = (OS.device == "iphone" || OS.device == "ipod") && OS.majorVersion < 7,
        SUPPORT_SWIPE_TO_GO_BACK = (OS.device == "iphone" || OS.device == "ipod") && OS.majorVersion >= 7,
        HISTORY_TRANSITION = SUPPORT_SWIPE_TO_GO_BACK ? "none" : null,
        BARCOMPENSATION = OS.browser == "mobilesafari" ? 60 : 0,
        STATUS_BAR_HEIGHT = 20,
        WINDOW = $(window),
        SCREEN = window.screen,
        HEAD = $("head"),

    // mobile app events
        INIT = "init",
        proxy = $.proxy;

    function osCssClass(os, options) {
        var classes = [];

        if (OS) {
            classes.push("km-on-" + OS.name);
        }

        if (os.skin) {
            classes.push("km-" + os.skin);
        } else {
            if (os.name == "ios" && os.majorVersion > 6) {
                classes.push("km-ios7");
            } else {
                classes.push("km-" + os.name);
            }
        }
        if ((os.name == "ios" && os.majorVersion < 7) || os.name != "ios") {
            classes.push("km-" + os.name + os.majorVersion);
        }
        classes.push("km-" + os.majorVersion);
        classes.push("km-m" + (os.minorVersion ? os.minorVersion[0] : 0));

        if (os.variant && ((os.skin && os.skin === os.name) || !os.skin)) {
            classes.push("km-" + (os.skin ? os.skin : os.name) + "-" + os.variant);
        }

        if (os.cordova) {
            classes.push("km-cordova");
        }
        if (os.appMode) {
            classes.push("km-app");
        } else {
            classes.push("km-web");
        }

        if (options && options.statusBarStyle) {
            classes.push("km-" + options.statusBarStyle + "-status-bar");
        }

        return classes.join(" ");
    }

    function wp8Background(os) {
        return 'km-wp-' + (os.noVariantSet ?
                            (parseInt($("<div style='background: Background' />").css("background-color").split(",")[1], 10) === 0 ? 'dark' : 'light') :
                            os.variant + " km-wp-" + os.variant + "-force");
    }

    function isOrientationHorizontal(element) {
        return OS.wp ? element.css("animation-name") == "-kendo-landscape" : (Math.abs(window.orientation) / 90 == 1);
    }

    function getOrientationClass(element) {
        return isOrientationHorizontal(element) ? HORIZONTAL : VERTICAL;
    }

    function setMinimumHeight(pane) {
        pane.parent().addBack()
               .css("min-height", window.innerHeight);
    }

    function applyViewportHeight() {
        $("meta[name=viewport]").remove();
        HEAD.append(viewportTemplate({
            height: ", width=device-width" +  // width=device-width was removed for iOS6, but this should stay for BB PhoneGap.
                        (isOrientationHorizontal() ?
                            ", height=" + window.innerHeight + "px" :
                            (support.mobileOS.flatVersion >= 600 && support.mobileOS.flatVersion < 700) ?
                                ", height=" + window.innerWidth + "px" :
                                ", height=device-height")
        }));
    }

    var Application = Widget.extend({
        init: function (element, options) {
            element = $(element);

            if (!element[0]) {
                element = $(document.body);
            }

            // global reference to current application
            mobile.application = this;
            Widget.fn.init.call(this, element, options);
            this.element.removeAttr("data-" + kendo.ns + "role");
            $($.proxy(this, 'bootstrap'));
        },

        bootstrap: function () {
            this._setupPlatform();
            this._attachMeta();
            this._setupElementClass();
            this._attachHideBarHandlers();
            var paneOptions = $.extend({}, this.options);
            delete paneOptions.name;

            var that = this,
                startHistory = function () {
                    that.pane = new Pane(that.element, paneOptions);
                    that.pane.navigateToInitial();

                    if (that.options.updateDocumentTitle) {
                        that._setupDocumentTitle();
                    }

                    that._startHistory();
                    that.trigger(INIT);
                };

            if (this.options.$angular) {
                setTimeout(startHistory);
            } else {
                startHistory();
            }
        },

        options: {
            name: "Application",
            hideAddressBar: true,
            browserHistory: true,
            historyTransition: HISTORY_TRANSITION,
            modelScope: window,
            statusBarStyle: "black",
            transition: "",
            platform: null,
            skin: null,
            updateDocumentTitle: true,
            useNativeScrolling: false
        },

        events: [
            INIT
        ],

        navigate: function (url, transition) {
            this.pane.navigate(url, transition);
        },

        replace: function (url, transition) {
            this.pane.replace(url, transition);
        },

        scroller: function () {
            return this.view().scroller;
        },

        hideLoading: function () {
            if (this.pane) {
                this.pane.hideLoading();
            } else {
                throw new Error("The mobile application instance is not fully instantiated. Please consider activating loading in the application init event handler.");
            }
        },

        showLoading: function () {
            if (this.pane) {
                this.pane.showLoading();
            } else {
                throw new Error("The mobile application instance is not fully instantiated. Please consider activating loading in the application init event handler.");
            }
        },

        changeLoadingMessage: function (message) {
            if (this.pane) {
                this.pane.changeLoadingMessage(message);
            } else {
                throw new Error("The mobile application instance is not fully instantiated. Please consider changing the message in the application init event handler.");
            }
        },

        view: function () {
            return this.pane.view();
        },

        skin: function (skin) {
            var that = this;

            if (!arguments.length) {
                return that.options.skin;
            }

            that.options.skin = skin || "";
            that.element[0].className = "km-pane";
            that._setupPlatform();
            that._setupElementClass();

            return that.options.skin;
        },

        destroy: function () {
            Widget.fn.destroy.call(this);
            this.pane.destroy();
            this.router.destroy();
        },

        _setupPlatform: function () {
            var that = this,
                platform = that.options.platform,
                skin = that.options.skin,
                split = [],
                os = OS || MOBILE_PLATFORMS[DEFAULT_OS];

            if (platform) {
                if (typeof platform === "string") {
                    split = platform.split("-");
                    os = $.extend({ variant: split[1] }, os, MOBILE_PLATFORMS[split[0]]);
                } else {
                    os = platform;
                }
            }

            if (skin) {
                split = skin.split("-");
                os = $.extend({}, os, { skin: split[0], variant: split[1] });
            }

            if (!os.variant) {
                os.noVariantSet = true;
                os.variant = "dark";
            }

            that.os = os;

            that.osCssClass = osCssClass(that.os, that.options);

            if (os.name == "wp") {
                if (!that.refreshBackgroundColorProxy) {
                    that.refreshBackgroundColorProxy = $.proxy(function () {
                        if (that.os.variant && (that.os.skin && that.os.skin === that.os.name) || !that.os.skin) {
                            that.element.removeClass("km-wp-dark km-wp-light km-wp-dark-force km-wp-light-force").addClass(wp8Background(that.os));
                        }
                    }, that);
                }

                $(document).off("visibilitychange", that.refreshBackgroundColorProxy);
                $(document).off("resume", that.refreshBackgroundColorProxy);

                if (!os.skin) {
                    that.element.parent().css("overflow", "hidden");

                    $(document).on("visibilitychange", that.refreshBackgroundColorProxy); // Restore theme on browser focus (using the Visibility API).
                    $(document).on("resume", that.refreshBackgroundColorProxy); // PhoneGap fires resume.

                    that.refreshBackgroundColorProxy();
                }
            }
        },

        _startHistory: function () {
            if (this.options.browserHistory) {
                this.router = new kendo.Router({ pushState: this.options.pushState, root: this.options.root, hashBang: this.options.hashBang });
                this.pane.bindToRouter(this.router);
                this.router.start();
            } else {
                if (!this.options.initial) {
                    this.pane.navigate("");
                }
            }
        },

        _resizeToScreenHeight: function () {
            var includeStatusBar = $("meta[name=apple-mobile-web-app-status-bar-style]").attr("content").match(/black-translucent|hidden/),
                element = this.element,
                height;

            if (CHROME) {
                height = window.innerHeight;
            } else {
                if (isOrientationHorizontal(element)) {
                    if (includeStatusBar) {
                        if (INITIALLY_HORIZONTAL) {
                            height = SCREEN.availWidth + STATUS_BAR_HEIGHT;
                        } else {
                            height = SCREEN.availWidth;
                        }
                    } else {
                        if (INITIALLY_HORIZONTAL) {
                            height = SCREEN.availWidth;
                        } else {
                            height = SCREEN.availWidth - STATUS_BAR_HEIGHT;
                        }
                    }
                } else {
                    if (includeStatusBar) {
                        if (INITIALLY_HORIZONTAL) {
                            height = SCREEN.availHeight;
                        } else {
                            height = SCREEN.availHeight + STATUS_BAR_HEIGHT;
                        }
                    } else {
                        if (INITIALLY_HORIZONTAL) {
                            height = SCREEN.availHeight - STATUS_BAR_HEIGHT;
                        } else {
                            height = SCREEN.availHeight;
                        }
                    }
                }
            }

            element.height(height);
        },

        _setupElementClass: function () {
            var that = this, size,
                element = that.element;

            element.parent().addClass("km-root km-" + (that.os.tablet ? "tablet" : "phone"));
            element.addClass(that.osCssClass + " " + getOrientationClass(element));
            if (this.options.useNativeScrolling) {
                element.parent().addClass("km-native-scrolling");
            }

            if (CHROME) {
                element.addClass("km-ios-chrome");
            }

            if (support.wpDevicePixelRatio) {
                element.parent().css("font-size", support.wpDevicePixelRatio + "em");
            }

            if (BERRYPHONEGAP) {
                applyViewportHeight();
            }
            if (that.options.useNativeScrolling) {
                element.parent().addClass("km-native-scrolling");
            } else if (ENABLE_CLIP) {
                size = (screen.availWidth > screen.availHeight ? screen.availWidth : screen.availHeight) + 200;
                $(clipTemplate({ width: size, height: size })).appendTo(HEAD);
            }

            if (BROKEN_WEBVIEW_RESIZE) {
                that._resizeToScreenHeight();
            }

            kendo.onResize(function () {
                element
                    .removeClass("km-horizontal km-vertical")
                    .addClass(getOrientationClass(element));

                if (that.options.useNativeScrolling) {
                    setMinimumHeight(element);
                }

                if (BROKEN_WEBVIEW_RESIZE) {
                    that._resizeToScreenHeight();
                }

                if (BERRYPHONEGAP) {
                    applyViewportHeight();
                }

                kendo.resize(element);
            });
        },

        _clearExistingMeta: function () {
            HEAD.find("meta")
                .filter("[name|='apple-mobile-web-app'],[name|='msapplication-tap'],[name='viewport']")
                .remove();
        },

        _attachMeta: function () {
            var options = this.options,
                icon = options.icon, size;

            this._clearExistingMeta();

            if (!BERRYPHONEGAP) {
                HEAD.prepend(viewportMeta);
            }

            HEAD.prepend(systemMeta(options));

            if (icon) {
                if (typeof icon === "string") {
                    icon = { "": icon };
                }

                for (size in icon) {
                    HEAD.prepend(iconMeta({ icon: icon[size], size: size }));
                }
            }

            if (options.useNativeScrolling) {
                setMinimumHeight(this.element);
            }
        },

        _attachHideBarHandlers: function () {
            var that = this,
                hideBar = proxy(that, "_hideBar");

            if (support.mobileOS.appMode || !that.options.hideAddressBar || !HIDEBAR || that.options.useNativeScrolling) {
                return;
            }

            that._initialHeight = {};

            WINDOW.on("load", hideBar);

            kendo.onResize(function () {
                setTimeout(window.scrollTo, 0, 0, 1);
            });
        },

        _setupDocumentTitle: function () {
            var that = this,
                defaultTitle = document.title;

            that.pane.bind("viewShow", function (e) {
                var title = e.view.title;
                document.title = title !== undefined ? title : defaultTitle;
            });
        },

        _hideBar: function () {
            var that = this,
                element = that.element;

            element.height(kendo.support.transforms.css + "calc(100% + " + BARCOMPENSATION + "px)");
            $(window).trigger(kendo.support.resize);
        }
    });

    kendo.mobile.Application = Application;
    kendo.ui.plugin(Application, kendo.mobile, 'Mobile');
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        mobile = kendo.mobile,
        ui = mobile.ui,
        Widget = ui.Widget,
        support = kendo.support,
        os = support.mobileOS,
        ANDROID3UP = os.android && os.flatVersion >= 300,
        CLICK = "click",
        DISABLED = "disabled",
        DISABLEDSTATE = "km-state-disabled";

    function highlightButton(widget, event, highlight) {
        $(event.target).closest(".km-button,.km-detail").toggleClass("km-state-active", highlight);

        if (ANDROID3UP && widget.deactivateTimeoutID) {
            clearTimeout(widget.deactivateTimeoutID);
            widget.deactivateTimeoutID = 0;
        }
    }

    function createBadge(value) {
        return $('<span class="km-badge">' + value + '</span>');
    }

    var Button = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            that._wrap();
            that._style();

            that.options.enable = that.options.enable && !that.element.attr(DISABLED);
            that.enable(that.options.enable);

            that._userEvents = new kendo.UserEvents(that.element, {
                press: function (e) { that._activate(e); },
                tap: function (e) { that._release(e); },
                release: function (e) { highlightButton(that, e, false); }
            });

            if (ANDROID3UP) {
                that.element.on("move", function (e) { that._timeoutDeactivate(e); });
            }
        },

        destroy: function () {
            Widget.fn.destroy.call(this);
            this._userEvents.destroy();
        },

        events: [
            CLICK
        ],

        options: {
            name: "Button",
            icon: "",
            style: "",
            badge: "",
            enable: true
        },

        badge: function (value) {
            var badge = this.badgeElement = this.badgeElement || createBadge(value).appendTo(this.element);

            if (value || value === 0) {
                badge.html(value);
                return this;
            }

            if (value === false) {
                badge.empty().remove();
                this.badgeElement = false;
                return this;
            }

            return badge.html();
        },

        enable: function (enable) {
            var element = this.element;

            if (typeof enable == "undefined") {
                enable = true;
            }

            this.options.enable = enable;

            if (enable) {
                element.removeAttr(DISABLED);
            } else {
                element.attr(DISABLED, DISABLED);
            }

            element.toggleClass(DISABLEDSTATE, !enable);
        },

        _timeoutDeactivate: function (e) {
            if (!this.deactivateTimeoutID) {
                this.deactivateTimeoutID = setTimeout(highlightButton, 500, this, e, false);
            }
        },

        _activate: function (e) {
            var activeElement = document.activeElement,
                nodeName = activeElement ? activeElement.nodeName : "";

            if (this.options.enable) {
                highlightButton(this, e, true);

                if (nodeName == "INPUT" || nodeName == "TEXTAREA") {
                    activeElement.blur(); // Hide device keyboard
                }
            }
        },

        _release: function (e) {
            var that = this;

            if (e.which > 1) {
                return;
            }

            if (!that.options.enable) {
                e.preventDefault();
                return;
            }

            if (that.trigger(CLICK, { target: $(e.target), button: that.element })) {
                e.preventDefault();
            }
        },

        _style: function () {
            var style = this.options.style,
                element = this.element,
                styles;

            if (style) {
                styles = style.split(" ");
                $.each(styles, function () {
                    element.addClass("km-" + this);
                });
            }
        },

        _wrap: function () {
            var that = this,
                icon = that.options.icon,
                badge = that.options.badge,
                iconSpan = '<span class="km-icon km-' + icon,
                element = that.element.addClass("km-button"),
                span = element.children("span:not(.km-icon)").addClass("km-text"),
                image = element.find("img").addClass("km-image");

            if (!span[0] && element.html()) {
                span = element.wrapInner('<span class="km-text" />').children("span.km-text");
            }

            if (!image[0] && icon) {
                if (!span[0]) {
                    iconSpan += " km-notext";
                }
                that.iconElement = element.prepend($(iconSpan + '" />'));
            }

            if (badge || badge === 0) {
                that.badgeElement = createBadge(badge).appendTo(element);
            }
        }
    });

    var BackButton = Button.extend({
        options: {
            name: "BackButton",
            style: "back"
        },

        init: function (element, options) {
            var that = this;
            Button.fn.init.call(that, element, options);

            if (typeof that.element.attr("href") === "undefined") {
                that.element.attr("href", "#:back");
            }
        }
    });

    var DetailButton = Button.extend({
        options: {
            name: "DetailButton",
            style: ""
        },

        init: function (element, options) {
            Button.fn.init.call(this, element, options);
        },

        _style: function () {
            var style = this.options.style + " detail",
                element = this.element;

            if (style) {
                var styles = style.split(" ");
                $.each(styles, function () {
                    element.addClass("km-" + this);
                });
            }
        },

        _wrap: function () {
            var that = this,
                icon = that.options.icon,
                iconSpan = '<span class="km-icon km-' + icon,
                element = that.element,
                span = element.children("span"),
                image = element.find("img").addClass("km-image");

            if (!image[0] && icon) {
                if (!span[0]) {
                    iconSpan += " km-notext";
                }
                element.prepend($(iconSpan + '" />'));
            }
        }

    });

    ui.plugin(Button);
    ui.plugin(BackButton);
    ui.plugin(DetailButton);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.mobile.ui,
        Widget = ui.Widget,
        ACTIVE = "km-state-active",
        DISABLE = "km-state-disabled",
        SELECT = "select",
        SELECTOR = "li:not(." + ACTIVE + ")";

    function createBadge(value) {
        return $('<span class="km-badge">' + value + '</span>');
    }

    var ButtonGroup = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            that.element.addClass("km-buttongroup").find("li").each(that._button);

            that.element.on(that.options.selectOn, SELECTOR, "_select");

            that._enable = true;
            that.select(that.options.index);

            if (!that.options.enable) {
                that._enable = false;
                that.wrapper.addClass(DISABLE);
            }
        },

        events: [
            SELECT
        ],

        options: {
            name: "ButtonGroup",
            selectOn: "down",
            index: -1,
            enable: true
        },

        current: function () {
            return this.element.find("." + ACTIVE);
        },

        select: function (li) {
            var that = this,
                index = -1;

            if (li === undefined || li === -1 || !that._enable || $(li).is("." + DISABLE)) {
                return;
            }

            that.current().removeClass(ACTIVE);

            if (typeof li === "number") {
                index = li;
                li = $(that.element[0].children[li]);
            } else if (li.nodeType) {
                li = $(li);
                index = li.index();
            }

            li.addClass(ACTIVE);
            that.selectedIndex = index;
        },

        badge: function (item, value) {
            var buttongroup = this.element, badge;

            if (!isNaN(item)) {
                item = buttongroup.children().get(item);
            }

            item = buttongroup.find(item);
            badge = $(item.children(".km-badge")[0] || createBadge(value).appendTo(item));

            if (value || value === 0) {
                badge.html(value);
                return this;
            }

            if (value === false) {
                badge.empty().remove();
                return this;
            }

            return badge.html();
        },

        enable: function (enable) {
            var wrapper = this.wrapper;

            if (typeof enable == "undefined") {
                enable = true;
            }

            if (enable) {
                wrapper.removeClass(DISABLE);
            } else {
                wrapper.addClass(DISABLE);
            }

            this._enable = this.options.enable = enable;
        },

        _button: function () {
            var button = $(this).addClass("km-button"),
                icon = kendo.attrValue(button, "icon"),
                badge = kendo.attrValue(button, "badge"),
                span = button.children("span"),
                image = button.find("img").addClass("km-image");

            if (!span[0]) {
                span = button.wrapInner("<span/>").children("span");
            }

            span.addClass("km-text");

            if (!image[0] && icon) {
                button.prepend($('<span class="km-icon km-' + icon + '"/>'));
            }

            if (badge || badge === 0) {
                createBadge(badge).appendTo(button);
            }
        },

        _select: function (e) {
            if (e.which > 1 || e.isDefaultPrevented() || !this._enable) {
                return;
            }

            this.select(e.currentTarget);
            this.trigger(SELECT, { index: this.selectedIndex });
        }
    });

    ui.plugin(ButtonGroup);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        Node = window.Node,
        mobile = kendo.mobile,
        ui = mobile.ui,
        DataSource = kendo.data.DataSource,
        Widget = ui.DataBoundWidget,
        ITEM_SELECTOR = ".km-list > li, > li:not(.km-group-container)",
        HIGHLIGHT_SELECTOR = ".km-listview-link, .km-listview-label",
        ICON_SELECTOR = "[" + kendo.attr("icon") + "]",
        proxy = $.proxy,
        attrValue = kendo.attrValue,
        GROUP_CLASS = "km-group-title",
        ACTIVE_CLASS = "km-state-active",
        GROUP_WRAPPER = '<div class="' + GROUP_CLASS + '"><div class="km-text"></div></div>',
        GROUP_TEMPLATE = kendo.template('<li><div class="' + GROUP_CLASS + '"><div class="km-text">#= this.headerTemplate(data) #</div></div><ul>#= kendo.render(this.template, data.items)#</ul></li>'),
        WRAPPER = '<div class="km-listview-wrapper" />',
        SEARCH_TEMPLATE = kendo.template('<form class="km-filter-form"><div class="km-filter-wrap"><input type="search" placeholder="#=placeholder#"/><a href="\\#" class="km-filter-reset" title="Clear"><span class="km-icon km-clear"></span><span class="km-text">Clear</span></a></div></form>'),
        NS = ".kendoMobileListView",
        STYLED = "styled",
        DATABOUND = "dataBound",
        DATABINDING = "dataBinding",
        ITEM_CHANGE = "itemChange",
        CLICK = "click",
        CHANGE = "change",
        PROGRESS = "progress",
        FUNCTION = "function",

        whitespaceRegExp = /^\s+$/,
        buttonRegExp = /button/;

    function whitespace() {
        return this.nodeType === Node.TEXT_NODE && this.nodeValue.match(whitespaceRegExp);
    }

    function addIcon(item, icon) {
        if (icon && !item[0].querySelector(".km-icon")) {
            item.prepend('<span class="km-icon km-' + icon + '"/>');
        }
    }

    function enhanceItem(item) {
        addIcon(item, attrValue(item, "icon"));
        addIcon(item, attrValue(item.children(ICON_SELECTOR), "icon"));
    }

    function enhanceLinkItem(item) {
        var parent = item.parent(),
            itemAndDetailButtons = item.add(parent.children(kendo.roleSelector("detailbutton"))),
            otherNodes = parent.contents().not(itemAndDetailButtons).not(whitespace);

        if (otherNodes.length) {
            return;
        }

        item.addClass("km-listview-link")
            .attr(kendo.attr("role"), "listview-link");

        addIcon(item, attrValue(parent, "icon"));
        addIcon(item, attrValue(item, "icon"));
    }

    function enhanceCheckBoxItem(label) {
        if (!label[0].querySelector("input[type=checkbox],input[type=radio]")) {
            return;
        }

        var item = label.parent();

        if (item.contents().not(label).not(function () { return this.nodeType == 3; })[0]) {
            return;
        }

        label.addClass("km-listview-label");

        label.children("[type=checkbox],[type=radio]").addClass("km-widget km-icon km-check");
    }

    function putAt(element, top) {
        $(element).css('transform', 'translate3d(0px, ' + top + 'px, 0px)');
    }

    var HeaderFixer = kendo.Class.extend({
        init: function (listView) {
            var scroller = listView.scroller();

            if (!scroller) {
                return;
            }

            this.options = listView.options;
            this.element = listView.element;
            this.scroller = listView.scroller();
            this._shouldFixHeaders();

            var headerFixer = this;

            var cacheHeaders = function () {
                headerFixer._cacheHeaders();
            };

            listView.bind("resize", cacheHeaders);

            listView.bind(STYLED, cacheHeaders);
            listView.bind(DATABOUND, cacheHeaders);

            scroller.bind("scroll", function (e) {
                headerFixer._fixHeader(e);
            });
        },

        _fixHeader: function (e) {
            if (!this.fixedHeaders) {
                return;
            }

            var i = 0,
                scroller = this.scroller,
                headers = this.headers,
                scrollTop = e.scrollTop,
                headerPair,
                offset,
                header;

            do {
                headerPair = headers[i++];
                if (!headerPair) {
                    header = $("<div />");
                    break;
                }
                offset = headerPair.offset;
                header = headerPair.header;
            } while (offset + 1 > scrollTop);

            if (this.currentHeader != i) {
                scroller.fixedContainer.html(header.clone());
                this.currentHeader = i;
            }
        },

        _shouldFixHeaders: function () {
            this.fixedHeaders = this.options.type === "group" && this.options.fixedHeaders;
        },

        _cacheHeaders: function () {
            this._shouldFixHeaders();

            if (!this.fixedHeaders) {
                return;
            }

            var headers = [], offset = this.scroller.scrollTop;

            this.element.find("." + GROUP_CLASS).each(function (_, header) {
                header = $(header);
                headers.unshift({
                    offset: header.position().top + offset,
                    header: header
                });
            });

            this.headers = headers;
            this._fixHeader({ scrollTop: offset });
        }
    });

    var DEFAULT_PULL_PARAMETERS = function () {
        return { page: 1 };
    };

    var RefreshHandler = kendo.Class.extend({
        init: function (listView) {
            var handler = this,
                options = listView.options,
                scroller = listView.scroller(),
                pullParameters = options.pullParameters || DEFAULT_PULL_PARAMETERS;

            this.listView = listView;
            this.scroller = scroller;

            listView.bind("_dataSource", function (e) {
                handler.setDataSource(e.dataSource);
            });

            scroller.setOptions({
                pullToRefresh: true,
                pull: function () {
                    if (!handler._pulled) {
                        handler._pulled = true;
                        handler.dataSource.read(pullParameters.call(listView, handler._first));
                    }
                },
                pullTemplate: options.messages.pullTemplate,
                releaseTemplate: options.messages.releaseTemplate,
                refreshTemplate: options.messages.refreshTemplate
            });
        },

        setDataSource: function (dataSource) {
            var handler = this;

            this._first = dataSource.view()[0];
            this.dataSource = dataSource;

            dataSource.bind("change", function () {
                handler._change();
            });

            dataSource.bind("error", function () {
                handler._change();
            });
        },

        _change: function () {
            var scroller = this.scroller,
                dataSource = this.dataSource;

            if (this._pulled) {
                scroller.pullHandled();
            }

            if (this._pulled || !this._first) {
                var view = dataSource.view();

                if (view[0]) {
                    this._first = view[0];
                }
            }

            this._pulled = false;
        }
    });

    var VirtualList = kendo.Observable.extend({
        init: function (options) {
            var list = this;

            kendo.Observable.fn.init.call(list);

            list.buffer = options.buffer;
            list.height = options.height;
            list.item = options.item;
            list.items = [];
            list.footer = options.footer;

            list.buffer.bind("reset", function () {
                list.refresh();
            });

        },

        refresh: function () {
            var buffer = this.buffer,
                items = this.items,
                endReached = false;

            while (items.length) {
                items.pop().destroy();
            }

            this.offset = buffer.offset;

            var itemConstructor = this.item,
                prevItem,
                item;

            for (var idx = 0; idx < buffer.viewSize; idx++) {
                if (idx === buffer.total()) {
                    endReached = true;
                    break;
                }
                item = itemConstructor(this.content(this.offset + items.length));
                item.below(prevItem);
                prevItem = item;
                items.push(item);
            }

            this.itemCount = items.length;

            this.trigger("reset");

            this._resize();

            if (endReached) {
                this.trigger("endReached");
            }
        },

        totalHeight: function () {
            if (!this.items[0]) {
                return 0;
            }

            var list = this,
                items = list.items,
                top = items[0].top,
                bottom = items[items.length - 1].bottom,
                averageItemHeight = (bottom - top) / list.itemCount,
                remainingItemsCount = list.buffer.length - list.offset - list.itemCount;

            return (this.footer ? this.footer.height : 0) + bottom + remainingItemsCount * averageItemHeight;
        },

        batchUpdate: function (top) {
            var height = this.height(),
                items = this.items,
                item,
                initialOffset = this.offset;

            if (!items[0]) {
                return;
            }

            if (this.lastDirection) { // scrolling up
                while (items[items.length - 1].bottom > top + height * 2) {
                    if (this.offset === 0) {
                        break;
                    }

                    this.offset--;
                    item = items.pop();
                    item.update(this.content(this.offset));
                    item.above(items[0]);
                    items.unshift(item);
                }
            } else { // scrolling down
                while (items[0].top < top - height) {
                    var nextIndex = this.offset + this.itemCount; // here, it should be offset + 1 + itemCount - 1.

                    if (nextIndex === this.buffer.total()) {
                        this.trigger("endReached");
                        break;
                    }

                    if (nextIndex === this.buffer.length) {
                        break;
                    }

                    item = items.shift();
                    item.update(this.content(this.offset + this.itemCount));
                    item.below(items[items.length - 1]);
                    items.push(item);
                    this.offset++;
                }
            }

            if (initialOffset !== this.offset) {
                this._resize();
            }
        },

        update: function (top) {
            var list = this,
                items = this.items,
                item,
                firstItem,
                lastItem,
                height = this.height(),
                itemCount = this.itemCount,
                padding = height / 2,
                up = (this.lastTop || 0) > top,
                topBorder = top - padding,
                bottomBorder = top + height + padding;

            if (!items[0]) {
                return;
            }

            this.lastTop = top;
            this.lastDirection = up;

            if (up) { // scrolling up
                if (items[0].top > topBorder &&  // needs reorder
                   items[items.length - 1].bottom > bottomBorder + padding && // enough padding below
                   this.offset > 0 // we are not at the top
                  ) {
                    this.offset--;
                    item = items.pop();
                    firstItem = items[0];
                    item.update(this.content(this.offset));
                    items.unshift(item);

                    item.above(firstItem);
                    list._resize();
                }
            } else { // scrolling down
                if (
                    items[items.length - 1].bottom < bottomBorder && // needs reorder
                    items[0].top < topBorder - padding // enough padding above
                ) {
                    var nextIndex = this.offset + itemCount; // here, it should be offset + 1 + itemCount - 1.

                    if (nextIndex === this.buffer.total()) {
                        this.trigger("endReached");
                    } else if (nextIndex !== this.buffer.length) {
                        item = items.shift();
                        lastItem = items[items.length - 1];
                        items.push(item);
                        item.update(this.content(this.offset + this.itemCount));
                        list.offset++;

                        item.below(lastItem);
                        list._resize();
                    }
                }
            }
        },

        content: function (index) {
            return this.buffer.at(index);
        },

        destroy: function () {
            this.unbind();
        },

        _resize: function () {
            var items = this.items,
                top = 0,
                bottom = 0,
                firstItem = items[0],
                lastItem = items[items.length - 1];

            if (firstItem) {
                top = firstItem.top;
                bottom = lastItem.bottom;
            }

            this.trigger("resize", { top: top, bottom: bottom });

            if (this.footer) {
                this.footer.below(lastItem);
            }
        }
    });

    // export for testing purposes
    kendo.mobile.ui.VirtualList = VirtualList;

    var VirtualListViewItem = kendo.Class.extend({
        init: function (listView, dataItem) {
            var element = listView.append([dataItem], true)[0],
                height = element.offsetHeight;

            $.extend(this, {
                top: 0,
                element: element,
                listView: listView,
                height: height,
                bottom: height
            });
        },

        update: function (dataItem) {
            this.element = this.listView.setDataItem(this.element, dataItem);
        },

        above: function (item) {
            if (item) {
                this.height = this.element.offsetHeight;
                this.top = item.top - this.height;
                this.bottom = item.top;
                putAt(this.element, this.top);
            }
        },

        below: function (item) {
            if (item) {
                this.height = this.element.offsetHeight;
                this.top = item.bottom;
                this.bottom = this.top + this.height;
                putAt(this.element, this.top);
            }
        },

        destroy: function () {
            kendo.destroy(this.element);
            $(this.element).remove();
        }
    });

    var LOAD_ICON = '<div><span class="km-icon"></span><span class="km-loading-left"></span><span class="km-loading-right"></span></div>';
    var VirtualListViewLoadingIndicator = kendo.Class.extend({
        init: function (listView) {
            this.element = $('<li class="km-load-more km-scroller-refresh" style="display: none"></li>').appendTo(listView.element);
            this._loadIcon = $(LOAD_ICON).appendTo(this.element);
        },

        enable: function () {
            this.element.show();
            this.height = this.element.outerHeight(true);
        },

        disable: function () {
            this.element.hide();
            this.height = 0;
        },

        below: function (item) {
            if (item) {
                this.top = item.bottom;
                this.bottom = this.height + this.top;
                putAt(this.element, this.top);
            }
        }
    });

    var VirtualListViewPressToLoadMore = VirtualListViewLoadingIndicator.extend({
        init: function (listView, buffer) {

            this._loadIcon = $(LOAD_ICON).hide();
            this._loadButton = $('<a class="km-load">' + listView.options.messages.loadMoreText + '</a>').hide();
            this.element = $('<li class="km-load-more" style="display: none"></li>').append(this._loadIcon).append(this._loadButton).appendTo(listView.element);

            var loadMore = this;

            this._loadButton.kendoMobileButton().data("kendoMobileButton").bind("click", function () {
                loadMore._hideShowButton();
                buffer.next();
            });

            buffer.bind("resize", function () {
                loadMore._showLoadButton();
            });

            this.height = this.element.outerHeight(true);
            this.disable();
        },

        _hideShowButton: function () {
            this._loadButton.hide();
            this.element.addClass("km-scroller-refresh");
            this._loadIcon.css('display', 'block');
        },

        _showLoadButton: function () {
            this._loadButton.show();
            this.element.removeClass("km-scroller-refresh");
            this._loadIcon.hide();
        }
    });

    var VirtualListViewItemBinder = kendo.Class.extend({
        init: function (listView) {
            var binder = this;

            this.chromeHeight = listView.wrapper.children().not(listView.element).outerHeight() || 0;
            this.listView = listView;
            this.scroller = listView.scroller();
            this.options = listView.options;

            listView.bind("_dataSource", function (e) {
                binder.setDataSource(e.dataSource, e.empty);
            });

            listView.bind("resize", function () {
                if (!binder.list.items.length) {
                    return;
                }

                binder.scroller.reset();
                binder.buffer.range(0);
                binder.list.refresh();
            });

            this.scroller.makeVirtual();

            this.scroller.bind("scroll", function (e) {
                binder.list.update(e.scrollTop);
            });

            this.scroller.bind("scrollEnd", function (e) {
                binder.list.batchUpdate(e.scrollTop);
            });
        },

        destroy: function () {
            this.list.unbind();
            this.buffer.unbind();
        },

        setDataSource: function (dataSource, empty) {
            var binder = this,
                options = this.options,
                listView = this.listView,
                scroller = listView.scroller(),
                pressToLoadMore = options.loadMore,
                pageSize,
                buffer,
                footer;

            this.dataSource = dataSource;

            pageSize = dataSource.pageSize() || options.virtualViewSize;

            if (!pageSize && !empty) {
                throw new Error("the DataSource does not have page size configured. Page Size setting is mandatory for the mobile listview virtual scrolling to work as expected.");
            }

            if (this.buffer) {
                this.buffer.destroy();
            }

            buffer = new kendo.data.Buffer(dataSource, Math.floor(pageSize / 2), pressToLoadMore);

            if (pressToLoadMore) {
                footer = new VirtualListViewPressToLoadMore(listView, buffer);
            } else {
                footer = new VirtualListViewLoadingIndicator(listView);
            }

            if (this.list) {
                this.list.destroy();
            }

            var list = new VirtualList({
                buffer: buffer,
                footer: footer,
                item: function (dataItem) { return new VirtualListViewItem(listView, dataItem); },
                height: function () { return scroller.height(); }
            });

            list.bind("resize", function () {
                binder.updateScrollerSize();
            });

            list.bind("reset", function () {
                binder.footer.enable();
            });

            list.bind("endReached", function () {
                footer.disable();
                binder.updateScrollerSize();
            });

            buffer.bind("expand", function () {
                list.lastDirection = false; // expand down
                list.batchUpdate(scroller.scrollTop);
            });

            $.extend(this, {
                buffer: buffer,
                scroller: scroller,
                list: list,
                footer: footer
            });
        },

        updateScrollerSize: function () {
            this.scroller.virtualSize(0, this.list.totalHeight() + this.chromeHeight);
        },

        refresh: function () {
            this.list.refresh();
        },

        reset: function () {
            this.buffer.range(0);
            this.list.refresh();
        }
    });

    var ListViewItemBinder = kendo.Class.extend({
        init: function (listView) {
            var binder = this;
            this.listView = listView;
            this.options = listView.options;

            var itemBinder = this;

            this._refreshHandler = function (e) {
                itemBinder.refresh(e);
            };

            this._progressHandler = function () {
                listView.showLoading();
            };

            listView.bind("_dataSource", function (e) {
                binder.setDataSource(e.dataSource);
            });
        },

        destroy: function () {
            this._unbindDataSource();
        },

        reset: function () { },

        refresh: function (e) {
            var action = e && e.action,
                dataItems = e && e.items,
                listView = this.listView,
                dataSource = this.dataSource,
                prependOnRefresh = this.options.appendOnRefresh,
                view = dataSource.view(),
                groups = dataSource.group(),
                groupedMode = groups && groups[0],
                item;

            if (action === "itemchange") {
                item = listView.findByDataItem(dataItems)[0];
                if (item) {
                    listView.setDataItem(item, dataItems[0]);
                }
                return;
            }

            var removedItems, addedItems, addedDataItems;
            var adding = (action === "add" && !groupedMode) || (prependOnRefresh && !listView._filter);
            var removing = action === "remove" && !groupedMode;

            if (adding) {
                // no need to unbind anything
                removedItems = [];
            } else if (removing) {
                // unbind the items about to be removed;
                removedItems = listView.findByDataItem(dataItems);
            }

            if (listView.trigger(DATABINDING, { action: action || "rebind", items: dataItems, removedItems: removedItems, index: e && e.index })) {
                if (this._shouldShowLoading()) {
                    listView.hideLoading();
                }
                return;
            }

            if (action === "add" && !groupedMode) {
                var index = view.indexOf(dataItems[0]);
                if (index > -1) {
                    addedItems = listView.insertAt(dataItems, index);
                    addedDataItems = dataItems;
                }
            } else if (action === "remove" && !groupedMode) {
                addedItems = [];
                listView.remove(dataItems);
            } else if (groupedMode) {
                listView.replaceGrouped(view);
            }
            else if (prependOnRefresh && !listView._filter) {
                addedItems = listView.prepend(view);
                addedDataItems = view;
            }
            else {
                listView.replace(view);
            }

            if (this._shouldShowLoading()) {
                listView.hideLoading();
            }

            listView.trigger(DATABOUND, { ns: ui, addedItems: addedItems, addedDataItems: addedDataItems });
        },

        setDataSource: function (dataSource) {
            if (this.dataSource) {
                this._unbindDataSource();
            }

            this.dataSource = dataSource;
            dataSource.bind(CHANGE, this._refreshHandler);

            if (this._shouldShowLoading()) {
                this.dataSource.bind(PROGRESS, this._progressHandler);
            }
        },

        _unbindDataSource: function () {
            this.dataSource.unbind(CHANGE, this._refreshHandler).unbind(PROGRESS, this._progressHandler);
        },

        _shouldShowLoading: function () {
            var options = this.options;
            return !options.pullToRefresh && !options.loadMore && !options.endlessScroll;
        }
    });

    var ListViewFilter = kendo.Class.extend({
        init: function (listView) {
            var filter = this,
                filterable = listView.options.filterable,
                events = "change paste";

            this.listView = listView;
            this.options = filterable;

            listView.element.before(SEARCH_TEMPLATE({ placeholder: filterable.placeholder || "Search..." }));

            if (filterable.autoFilter !== false) {
                events += " keyup";
            }

            this.element = listView.wrapper.find(".km-search-form");

            this.searchInput = listView.wrapper.find("input[type=search]")
                .closest("form").on("submit" + NS, function (e) {
                    e.preventDefault();
                })
                .end()
                .on("focus" + NS, function () {
                    filter._oldFilter = filter.searchInput.val();
                })
                .on(events.split(" ").join(NS + " ") + NS, proxy(this._filterChange, this));

            this.clearButton = listView.wrapper.find(".km-filter-reset")
                .on(CLICK, proxy(this, "_clearFilter"))
                .hide();
        },

        _search: function (expr) {
            this._filter = true;
            this.clearButton[expr ? "show" : "hide"]();
            this.listView.dataSource.filter(expr);
        },

        _filterChange: function (e) {
            var filter = this;
            if (e.type == "paste" && this.options.autoFilter !== false) {
                setTimeout(function () {
                    filter._applyFilter();
                }, 1);
            } else {
                this._applyFilter();
            }
        },

        _applyFilter: function () {
            var options = this.options,
                value = this.searchInput.val(),
                expr = value.length ? {
                    field: options.field,
                    operator: options.operator || "startsWith",
                    ignoreCase: options.ignoreCase,
                    value: value
                } : null;

            if (value === this._oldFilter) {
                return;
            }

            this._oldFilter = value;
            this._search(expr);
        },

        _clearFilter: function (e) {
            this.searchInput.val("");
            this._search(null);

            e.preventDefault();
        }
    });

    var ListView = Widget.extend({
        init: function (element, options) {
            var listView = this;

            Widget.fn.init.call(this, element, options);

            element = this.element;

            options = this.options;

            // support for legacy typo in configuration options: scrollTreshold -> scrollThreshold.
            if (options.scrollTreshold) {
                options.scrollThreshold = options.scrollTreshold;
            }

            element
                .on("down", HIGHLIGHT_SELECTOR, "_highlight")
                .on("move up cancel", HIGHLIGHT_SELECTOR, "_dim");

            this._userEvents = new kendo.UserEvents(element, {
                filter: ITEM_SELECTOR,
                allowSelection: true,
                tap: function (e) {
                    listView._click(e);
                }
            });

            // HACK!!! to negate the ms touch action from the user events.
            element.css("-ms-touch-action", "auto");

            element.wrap(WRAPPER);

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

            this._headerFixer = new HeaderFixer(this);

            this._itemsCache = {};
            this._templates();

            this.virtual = options.endlessScroll || options.loadMore;

            this._style();

            if (this.options.filterable) {
                this._filter = new ListViewFilter(this);
            }

            if (this.virtual) {
                this._itemBinder = new VirtualListViewItemBinder(this);
            } else {
                this._itemBinder = new ListViewItemBinder(this);
            }

            if (this.options.pullToRefresh) {
                this._pullToRefreshHandler = new RefreshHandler(this);
            }

            this.setDataSource(options.dataSource);

            this._enhanceItems(this.items());

            kendo.notify(this, ui);
        },

        events: [
            CLICK,
            DATABINDING,
            DATABOUND,
            ITEM_CHANGE
        ],

        options: {
            name: "ListView",
            style: "",
            type: "flat",
            autoBind: true,
            fixedHeaders: false,
            template: "#:data#",
            headerTemplate: '<span class="km-text">#:value#</span>',
            appendOnRefresh: false,
            loadMore: false,
            endlessScroll: false,
            scrollThreshold: 30,
            pullToRefresh: false,
            messages: {
                loadMoreText: "Press to load more",
                pullTemplate: "Pull to refresh",
                releaseTemplate: "Release to refresh",
                refreshTemplate: "Refreshing"
            },
            pullOffset: 140,
            filterable: false,
            virtualViewSize: null
        },

        refresh: function () {
            this._itemBinder.refresh();
        },

        reset: function () {
            this._itemBinder.reset();
        },

        setDataSource: function (dataSource) {
            // the listView should have a ready datasource for MVVM to function properly. But an empty datasource should not empty the element
            var emptyDataSource = !dataSource;
            this.dataSource = DataSource.create(dataSource);

            this.trigger("_dataSource", { dataSource: this.dataSource, empty: emptyDataSource });

            if (this.options.autoBind && !emptyDataSource) {
                this.items().remove();
                this.dataSource.fetch();
            }
        },

        destroy: function () {
            Widget.fn.destroy.call(this);
            kendo.destroy(this.element);
            this._userEvents.destroy();
            if (this._itemBinder) {
                this._itemBinder.destroy();
            }

            this.element.unwrap();
            delete this.element;
            delete this.wrapper;
            delete this._userEvents;
        },

        items: function () {
            if (this.options.type === "group") {
                return this.element.find(".km-list").children();
            } else {
                return this.element.children().not('.km-load-more');
            }
        },

        scroller: function () {
            if (!this._scrollerInstance) {
                this._scrollerInstance = this.element.closest(".km-scroll-wrapper").data("kendoMobileScroller");
            }

            return this._scrollerInstance;
        },

        showLoading: function () {
            var view = this.view();
            if (view && view.loader) {
                view.loader.show();
            }
        },

        hideLoading: function () {
            var view = this.view();
            if (view && view.loader) {
                view.loader.hide();
            }
        },

        insertAt: function (dataItems, index, triggerChange) {
            var listView = this;
            return listView._renderItems(dataItems, function (items) {
                if (index === 0) {
                    listView.element.prepend(items);
                }
                else if (index === -1) {
                    listView.element.append(items);
                } else {
                    listView.items().eq(index - 1).after(items);
                }

                if (triggerChange) {
                    for (var i = 0; i < items.length; i++) {
                        listView.trigger(ITEM_CHANGE, { item: items.eq(i), data: dataItems[i], ns: ui });
                    }
                }
            });
        },

        append: function (dataItems, triggerChange) {
            return this.insertAt(dataItems, -1, triggerChange);
        },

        prepend: function (dataItems, triggerChange) {
            return this.insertAt(dataItems, 0, triggerChange);
        },

        replace: function (dataItems) {
            this.options.type = "flat";
            this._angularItems("cleanup");
            this.element.empty();
            this._style();
            return this.insertAt(dataItems, 0);
        },

        replaceGrouped: function (groups) {
            this.options.type = "group";
            this._angularItems("cleanup");
            this.element.empty();
            var items = $(kendo.render(this.groupTemplate, groups));

            this._enhanceItems(items.children("ul").children("li"));
            this.element.append(items);
            mobile.init(items);
            this._style();
            this._angularItems("compile");
        },

        remove: function (dataItems) {
            var items = this.findByDataItem(dataItems);
            this.angular("cleanup", function () {
                return { elements: items };
            });
            kendo.destroy(items);
            items.remove();
        },

        findByDataItem: function (dataItems) {
            var selectors = [];

            for (var idx = 0, length = dataItems.length; idx < length; idx++) {
                selectors[idx] = "[data-" + kendo.ns + "uid=" + dataItems[idx].uid + "]";
            }

            return this.element.find(selectors.join(","));
        },

        // item is a DOM element, not jQuery object.
        setDataItem: function (item, dataItem) {
            var listView = this,
                replaceItem = function (items) {
                    var newItem = $(items[0]);
                    kendo.destroy(item);
                    $(item).replaceWith(newItem);
                    listView.trigger(ITEM_CHANGE, { item: newItem, data: dataItem, ns: ui });
                };

            return this._renderItems([dataItem], replaceItem)[0];
        },

        _renderItems: function (dataItems, callback) {
            var items = $(kendo.render(this.template, dataItems));

            this.angular("compile", function () {
                return {
                    elements: items,
                    data: dataItems.map(function (data) {
                        return { dataItem: data };
                    })
                };
            });

            callback(items);
            mobile.init(items);
            this._enhanceItems(items);

            return items;
        },

        _dim: function (e) {
            this._toggle(e, false);
        },

        _highlight: function (e) {
            this._toggle(e, true);
        },

        _toggle: function (e, highlight) {
            if (e.which > 1) {
                return;
            }

            var clicked = $(e.currentTarget),
                item = clicked.parent(),
                role = attrValue(clicked, "role") || "",
                plainItem = (!role.match(buttonRegExp)),
                prevented = e.isDefaultPrevented();

            if (plainItem) {
                item.toggleClass(ACTIVE_CLASS, highlight && !prevented);
            }
        },

        _templates: function () {
            var template = this.options.template,
                headerTemplate = this.options.headerTemplate,
                dataIDAttribute = ' data-uid="#=arguments[0].uid || ""#"',
                templateProxy = {},
                groupTemplateProxy = {};

            if (typeof template === FUNCTION) {
                templateProxy.template = template;
                template = "#=this.template(data)#";
            }

            this.template = proxy(kendo.template("<li" + dataIDAttribute + ">" + template + "</li>"), templateProxy);

            groupTemplateProxy.template = this.template;

            if (typeof headerTemplate === FUNCTION) {
                groupTemplateProxy._headerTemplate = headerTemplate;
                headerTemplate = "#=this._headerTemplate(data)#";
            }

            groupTemplateProxy.headerTemplate = kendo.template(headerTemplate);

            this.groupTemplate = proxy(GROUP_TEMPLATE, groupTemplateProxy);
        },

        _click: function (e) {
            if (e.event.which > 1 || e.event.isDefaultPrevented()) {
                return;
            }

            var dataItem,
                item = e.target,
                target = $(e.event.target),
                buttonElement = target.closest(kendo.roleSelector("button", "detailbutton", "backbutton")),
                button = kendo.widgetInstance(buttonElement, ui),
                id = item.attr(kendo.attr("uid"));

            if (id) {
                dataItem = this.dataSource.getByUid(id);
            }

            if (this.trigger(CLICK, { target: target, item: item, dataItem: dataItem, button: button })) {
                e.preventDefault();
            }
        },

        _styleGroups: function () {
            var rootItems = this.element.children();

            rootItems.children("ul").addClass("km-list");

            rootItems.each(function () {
                var li = $(this),
                    groupHeader = li.contents().first();

                li.addClass("km-group-container");
                if (!groupHeader.is("ul") && !groupHeader.is("div." + GROUP_CLASS)) {
                    groupHeader.wrap(GROUP_WRAPPER);
                }
            });
        },

        _style: function () {
            var options = this.options,
                grouped = options.type === "group",
                element = this.element,
                inset = options.style === "inset";

            element.addClass("km-listview")
                .toggleClass("km-list", !grouped)
                .toggleClass("km-virtual-list", this.virtual)
                .toggleClass("km-listinset", !grouped && inset)
                .toggleClass("km-listgroup", grouped && !inset)
                .toggleClass("km-listgroupinset", grouped && inset);

            if (!element.parents(".km-listview")[0]) {
                element.closest(".km-content").toggleClass("km-insetcontent", inset); // iOS has white background when the list is not inset.
            }

            if (grouped) {
                this._styleGroups();
            }

            this.trigger(STYLED);
        },

        _enhanceItems: function (items) {
            items.each(function () {
                var item = $(this),
                    child,
                    enhanced = false;

                item.children().each(function () {
                    child = $(this);
                    if (child.is("a")) {
                        enhanceLinkItem(child);
                        enhanced = true;
                    } else if (child.is("label")) {
                        enhanceCheckBoxItem(child);
                        enhanced = true;
                    }
                });

                if (!enhanced) {
                    enhanceItem(item);
                }
            });
        }
    });

    ui.plugin(ListView);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        mobile = kendo.mobile,
        ui = mobile.ui,
        Widget = ui.Widget;

    function createContainer(align, element) {
        var items = element.find("[" + kendo.attr("align") + "=" + align + "]");

        if (items[0]) {
            return $('<div class="km-' + align + 'item" />').append(items).prependTo(element);
        }
    }

    function toggleTitle(centerElement) {
        var siblings = centerElement.siblings(),
            noTitle = !!centerElement.children("ul")[0],
            showTitle = (!!siblings[0] && $.trim(centerElement.text()) === "");

        centerElement.prevAll().toggleClass("km-absolute", noTitle);
        centerElement.toggleClass("km-show-title", showTitle);
        centerElement.toggleClass("km-fill-title", showTitle && !$.trim(centerElement.html()));
        centerElement.toggleClass("km-no-title", noTitle);
        centerElement.toggleClass("km-hide-title", centerElement.css("visibility") == "hidden" && !siblings.children().is(":visible"));
    }

    var NavBar = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            element = that.element;

            that.container().bind("show", $.proxy(this, "refresh"));

            element.addClass("km-navbar").wrapInner($('<div class="km-view-title km-show-title" />'));
            that.leftElement = createContainer("left", element);
            that.rightElement = createContainer("right", element);
            that.centerElement = element.find(".km-view-title");
        },

        options: {
            name: "NavBar"
        },

        title: function (value) {
            this.element.find(kendo.roleSelector("view-title")).text(value);
            toggleTitle(this.centerElement);
        },

        refresh: function (e) {
            var view = e.view;
            if (view.options.title) {
                this.title(view.options.title);
            } else {
                toggleTitle(this.centerElement);
            }
        },

        destroy: function () {
            Widget.fn.destroy.call(this);
            kendo.destroy(this.element);
        }
    });

    ui.plugin(NavBar);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        mobile = kendo.mobile,
        ui = mobile.ui,
        proxy = $.proxy,
        Transition = kendo.effects.Transition,
        Pane = kendo.ui.Pane,
        PaneDimensions = kendo.ui.PaneDimensions,
        Widget = ui.DataBoundWidget,
        DataSource = kendo.data.DataSource,
        Buffer = kendo.data.Buffer,
        BatchBuffer = kendo.data.BatchBuffer,

    // Math
        math = Math,
        abs = math.abs,
        ceil = math.ceil,
        round = math.round,
        max = math.max,
        min = math.min,
        floor = math.floor,

        CHANGE = "change",
        CHANGING = "changing",
        REFRESH = "refresh",
        CURRENT_PAGE_CLASS = "km-current-page",
        VIRTUAL_PAGE_CLASS = "km-virtual-page",
        FUNCTION = "function",
        ITEM_CHANGE = "itemChange",
        CLEANUP = "cleanup",

        VIRTUAL_PAGE_COUNT = 3,
        LEFT_PAGE = -1,
        CETER_PAGE = 0,
        RIGHT_PAGE = 1,

        LEFT_SWIPE = -1,
        NUDGE = 0,
        RIGHT_SWIPE = 1;

    var Pager = kendo.Class.extend({
        init: function (scrollView) {
            var that = this,
                element = $("<ol class='km-pages'/>");

            scrollView.element.append(element);

            this._changeProxy = proxy(that, "_change");
            this._refreshProxy = proxy(that, "_refresh");
            scrollView.bind(CHANGE, this._changeProxy);
            scrollView.bind(REFRESH, this._refreshProxy);

            $.extend(that, { element: element, scrollView: scrollView });
        },

        items: function () {
            return this.element.children();
        },

        _refresh: function (e) {
            var pageHTML = "";

            for (var idx = 0; idx < e.pageCount; idx++) {
                pageHTML += "<li/>";
            }

            this.element.html(pageHTML);
            this.items().eq(e.page).addClass(CURRENT_PAGE_CLASS);
        },

        _change: function (e) {
            this.items().removeClass(CURRENT_PAGE_CLASS).eq(e.page).addClass(CURRENT_PAGE_CLASS);
        },

        destroy: function () {
            this.scrollView.unbind(CHANGE, this._changeProxy);
            this.scrollView.unbind(REFRESH, this._refreshProxy);
            this.element.remove();
        }
    });

    kendo.mobile.ui.ScrollViewPager = Pager;

    var TRANSITION_END = "transitionEnd",
        DRAG_START = "dragStart",
        DRAG_END = "dragEnd";

    var ElasticPane = kendo.Observable.extend({
        init: function (element, options) {
            var that = this;

            kendo.Observable.fn.init.call(this);

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

            var movable,
                transition,
                userEvents,
                dimensions,
                dimension,
                pane;

            movable = new kendo.ui.Movable(that.element);

            transition = new Transition({
                axis: "x",
                movable: movable,
                onEnd: function () {
                    that.trigger(TRANSITION_END);
                }
            });

            userEvents = new kendo.UserEvents(element, {
                start: function (e) {
                    if (abs(e.x.velocity) * 2 >= abs(e.y.velocity)) {
                        userEvents.capture();
                    } else {
                        userEvents.cancel();
                    }

                    that.trigger(DRAG_START, e);
                    transition.cancel();
                },
                allowSelection: true,
                end: function (e) {
                    that.trigger(DRAG_END, e);
                }
            });

            dimensions = new PaneDimensions({
                element: that.element,
                container: that.container
            });

            dimension = dimensions.x;

            dimension.bind(CHANGE, function () {
                that.trigger(CHANGE);
            });

            pane = new Pane({
                dimensions: dimensions,
                userEvents: userEvents,
                movable: movable,
                elastic: true
            });

            $.extend(that, {
                duration: options && options.duration || 1,
                movable: movable,
                transition: transition,
                userEvents: userEvents,
                dimensions: dimensions,
                dimension: dimension,
                pane: pane
            });

            this.bind([TRANSITION_END, DRAG_START, DRAG_END, CHANGE], options);
        },

        size: function () {
            return { width: this.dimensions.x.getSize(), height: this.dimensions.y.getSize() };
        },

        total: function () {
            return this.dimension.getTotal();
        },

        offset: function () {
            return -this.movable.x;
        },

        updateDimension: function () {
            this.dimension.update(true);
        },

        refresh: function () {
            this.dimensions.refresh();
        },

        moveTo: function (offset) {
            this.movable.moveAxis("x", -offset);
        },

        transitionTo: function (offset, ease, instant) {
            if (instant) {
                this.moveTo(-offset);
            } else {
                this.transition.moveTo({ location: offset, duration: this.duration, ease: ease });
            }
        }
    });

    kendo.mobile.ui.ScrollViewElasticPane = ElasticPane;

    var ScrollViewContent = kendo.Observable.extend({
        init: function (element, pane, options) {
            var that = this;

            kendo.Observable.fn.init.call(this);
            that.element = element;
            that.pane = pane;
            that._getPages();
            this.page = 0;
            this.pageSize = options.pageSize || 1;
            this.contentHeight = options.contentHeight;
            this.enablePager = options.enablePager;
        },

        scrollTo: function (page, instant) {
            this.page = page;
            this.pane.transitionTo(-page * this.pane.size().width, Transition.easeOutExpo, instant);
        },

        paneMoved: function (swipeType, bounce, callback, /*internal*/instant) {
            var that = this,
                pane = that.pane,
                width = pane.size().width * that.pageSize,
                approx = round,
                ease = bounce ? Transition.easeOutBack : Transition.easeOutExpo,
                snap,
                nextPage;

            if (swipeType === LEFT_SWIPE) {
                approx = ceil;
            } else if (swipeType === RIGHT_SWIPE) {
                approx = floor;
            }

            nextPage = approx(pane.offset() / width);

            snap = max(that.minSnap, min(-nextPage * width, that.maxSnap));

            if (nextPage != that.page) {
                if (callback && callback({ currentPage: that.page, nextPage: nextPage })) {
                    snap = -that.page * pane.size().width;
                }
            }

            pane.transitionTo(snap, ease, instant);
        },

        updatePage: function () {
            var pane = this.pane,
                page = round(pane.offset() / pane.size().width);

            if (page != this.page) {
                this.page = page;
                return true;
            }

            return false;
        },

        forcePageUpdate: function () {
            return this.updatePage();
        },

        resizeTo: function (size) {
            var pane = this.pane,
                width = size.width;

            this.pageElements.width(width);

            if (this.contentHeight === "100%") {
                var containerHeight = this.element.parent().height();

                if (this.enablePager === true) {
                    var pager = this.element.parent().find("ol.km-pages");
                    if (pager.length) {
                        containerHeight -= pager.outerHeight(true);
                    }
                }

                this.element.css("height", containerHeight);
                this.pageElements.css("height", containerHeight);
            }

            // re-read pane dimension after the pageElements have been resized.
            pane.updateDimension();

            if (!this._paged) {
                this.page = floor(pane.offset() / width);
            }

            this.scrollTo(this.page, true);

            this.pageCount = ceil(pane.total() / width);
            this.minSnap = -(this.pageCount - 1) * width;
            this.maxSnap = 0;
        },

        _getPages: function () {
            this.pageElements = this.element.find(kendo.roleSelector("page"));
            this._paged = this.pageElements.length > 0;
        }
    });

    kendo.mobile.ui.ScrollViewContent = ScrollViewContent;

    var VirtualScrollViewContent = kendo.Observable.extend({
        init: function (element, pane, options) {
            var that = this;

            kendo.Observable.fn.init.call(this);

            that.element = element;
            that.pane = pane;
            that.options = options;
            that._templates();
            that.page = options.page || 0;
            that.pages = [];
            that._initPages();
            that.resizeTo(that.pane.size());

            that.pane.dimension.forceEnabled();
        },

        setDataSource: function (dataSource) {
            this.dataSource = DataSource.create(dataSource);
            this._buffer();
            this._pendingPageRefresh = false;
            this._pendingWidgetRefresh = false;
        },

        _viewShow: function () {
            var that = this;
            if (that._pendingWidgetRefresh) {
                setTimeout(function () {
                    that._resetPages();
                }, 0);
                that._pendingWidgetRefresh = false;
            }
        },

        _buffer: function () {
            var itemsPerPage = this.options.itemsPerPage;

            if (this.buffer) {
                this.buffer.destroy();
            }

            if (itemsPerPage > 1) {
                this.buffer = new BatchBuffer(this.dataSource, itemsPerPage);
            } else {
                this.buffer = new Buffer(this.dataSource, itemsPerPage * 3);
            }

            this._resizeProxy = proxy(this, "_onResize");
            this._resetProxy = proxy(this, "_onReset");
            this._endReachedProxy = proxy(this, "_onEndReached");

            this.buffer.bind({
                "resize": this._resizeProxy,
                "reset": this._resetProxy,
                "endreached": this._endReachedProxy
            });
        },

        _templates: function () {
            var template = this.options.template,
                emptyTemplate = this.options.emptyTemplate,
                templateProxy = {},
                emptyTemplateProxy = {};

            if (typeof template === FUNCTION) {
                templateProxy.template = template;
                template = "#=this.template(data)#";
            }

            this.template = proxy(kendo.template(template), templateProxy);

            if (typeof emptyTemplate === FUNCTION) {
                emptyTemplateProxy.emptyTemplate = emptyTemplate;
                emptyTemplate = "#=this.emptyTemplate(data)#";
            }

            this.emptyTemplate = proxy(kendo.template(emptyTemplate), emptyTemplateProxy);
        },

        _initPages: function () {
            var pages = this.pages,
                element = this.element,
                page;

            for (var i = 0; i < VIRTUAL_PAGE_COUNT; i++) {
                page = new Page(element);
                pages.push(page);
            }

            this.pane.updateDimension();
        },

        resizeTo: function (size) {
            var pages = this.pages,
                pane = this.pane;

            for (var i = 0; i < pages.length; i++) {
                pages[i].setWidth(size.width);
            }

            if (this.options.contentHeight === "auto") {
                this.element.css("height", this.pages[1].element.height());
            }

            else if (this.options.contentHeight === "100%") {
                var containerHeight = this.element.parent().height();

                if (this.options.enablePager === true) {
                    var pager = this.element.parent().find("ol.km-pages");
                    if (pager.length) {
                        containerHeight -= pager.outerHeight(true);
                    }
                }

                this.element.css("height", containerHeight);
                pages[0].element.css("height", containerHeight);
                pages[1].element.css("height", containerHeight);
                pages[2].element.css("height", containerHeight);
            }

            pane.updateDimension();

            this._repositionPages();

            this.width = size.width;
        },

        scrollTo: function (page) {
            var buffer = this.buffer,
                dataItem;

            buffer.syncDataSource();
            dataItem = buffer.at(page);

            if (!dataItem) {
                return;
            }

            this._updatePagesContent(page);

            this.page = page;
        },

        paneMoved: function (swipeType, bounce, callback, /*internal*/instant) {
            var that = this,
                pane = that.pane,
                width = pane.size().width,
                offset = pane.offset(),
                thresholdPassed = Math.abs(offset) >= width / 3,
                ease = bounce ? kendo.effects.Transition.easeOutBack : kendo.effects.Transition.easeOutExpo,
                isEndReached = that.page + 2 > that.buffer.total(),
                nextPage,
                delta = 0;

            if (swipeType === RIGHT_SWIPE) {
                if (that.page !== 0) {
                    delta = -1; //backward
                }
            } else if (swipeType === LEFT_SWIPE && !isEndReached) {
                delta = 1; //forward
            } else if (offset > 0 && (thresholdPassed && !isEndReached)) {
                delta = 1; //forward
            } else if (offset < 0 && thresholdPassed) {
                if (that.page !== 0) {
                    delta = -1; //backward
                }
            }

            nextPage = that.page;
            if (delta) {
                nextPage = (delta > 0) ? nextPage + 1 : nextPage - 1;
            }

            if (callback && callback({ currentPage: that.page, nextPage: nextPage })) {
                delta = 0;
            }

            if (delta === 0) {
                that._cancelMove(ease, instant);
            } else if (delta === -1) {
                that._moveBackward(instant);
            } else if (delta === 1) {
                that._moveForward(instant);
            }
        },

        updatePage: function () {
            var pages = this.pages;

            if (this.pane.offset() === 0) {
                return false;
            }

            if (this.pane.offset() > 0) {
                pages.push(this.pages.shift()); //forward
                this.page++;
                this.setPageContent(pages[2], this.page + 1);
            } else {
                pages.unshift(this.pages.pop()); //back
                this.page--;
                this.setPageContent(pages[0], this.page - 1);
            }

            this._repositionPages();

            this._resetMovable();

            return true;
        },

        forcePageUpdate: function () {
            var offset = this.pane.offset(),
                threshold = this.pane.size().width * 3 / 4;

            if (abs(offset) > threshold) {
                return this.updatePage();
            }

            return false;
        },

        _resetMovable: function () {
            this.pane.moveTo(0);
        },

        _moveForward: function (instant) {
            this.pane.transitionTo(-this.width, kendo.effects.Transition.easeOutExpo, instant);
        },

        _moveBackward: function (instant) {
            this.pane.transitionTo(this.width, kendo.effects.Transition.easeOutExpo, instant);
        },

        _cancelMove: function (ease, /*internal*/instant) {
            this.pane.transitionTo(0, ease, instant);
        },

        _resetPages: function () {
            this.page = this.options.page || 0;

            this._updatePagesContent(this.page);
            this._repositionPages();

            this.trigger("reset");
        },

        _onResize: function () {
            this.pageCount = ceil(this.dataSource.total() / this.options.itemsPerPage);

            if (this._pendingPageRefresh) {
                this._updatePagesContent(this.page);
                this._pendingPageRefresh = false;
            }

            this.trigger("resize");
        },

        _onReset: function () {
            this.pageCount = ceil(this.dataSource.total() / this.options.itemsPerPage);
            this._resetPages();
        },

        _onEndReached: function () {
            this._pendingPageRefresh = true;
        },

        _repositionPages: function () {
            var pages = this.pages;

            pages[0].position(LEFT_PAGE);
            pages[1].position(CETER_PAGE);
            pages[2].position(RIGHT_PAGE);
        },

        _updatePagesContent: function (offset) {
            var pages = this.pages,
                currentPage = offset || 0;

            this.setPageContent(pages[0], currentPage - 1);
            this.setPageContent(pages[1], currentPage);
            this.setPageContent(pages[2], currentPage + 1);
        },

        setPageContent: function (page, index) {
            var buffer = this.buffer,
                template = this.template,
                emptyTemplate = this.emptyTemplate,
                view = null;

            if (index >= 0) {
                view = buffer.at(index);
                if ($.isArray(view) && !view.length) {
                    view = null;
                }
            }

            this.trigger(CLEANUP, { item: page.element });

            if (view !== null) {
                page.content(template(view));
            } else {
                page.content(emptyTemplate({}));
            }

            kendo.mobile.init(page.element);
            this.trigger(ITEM_CHANGE, { item: page.element, data: view, ns: kendo.mobile.ui });

        }
    });

    kendo.mobile.ui.VirtualScrollViewContent = VirtualScrollViewContent;

    var Page = kendo.Class.extend({
        init: function (container) {
            this.element = $("<div class='" + VIRTUAL_PAGE_CLASS + "'></div>");
            this.width = container.width();
            this.element.width(this.width);
            container.append(this.element);
        },

        content: function (theContent) {
            this.element.html(theContent);
        },

        position: function (position) { //position can be -1, 0, 1
            this.element.css("transform", "translate3d(" + this.width * position + "px, 0, 0)");
        },

        setWidth: function (width) {
            this.width = width;
            this.element.width(width);
        }
    });

    kendo.mobile.ui.VirtualPage = Page;

    var ScrollView = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);

            options = that.options;
            element = that.element;

            kendo.stripWhitespace(element[0]);

            element
                .wrapInner("<div/>")
                .addClass("km-scrollview");

            if (this.options.enablePager) {
                this.pager = new Pager(this);
            }

            that.inner = element.children().first();
            that.page = 0;
            that.inner.css("height", options.contentHeight);

            that.pane = new ElasticPane(that.inner, {
                duration: this.options.duration,
                transitionEnd: proxy(this, "_transitionEnd"),
                dragStart: proxy(this, "_dragStart"),
                dragEnd: proxy(this, "_dragEnd"),
                change: proxy(this, REFRESH)
            });

            that.bind("resize", function () {
                that.pane.refresh();
            });

            that.page = options.page;

            var empty = this.inner.children().length === 0;

            var content = empty ? new VirtualScrollViewContent(that.inner, that.pane, options) : new ScrollViewContent(that.inner, that.pane, options);

            content.page = that.page;

            content.bind("reset", function () {
                that._syncWithContent();
                that.trigger(REFRESH, { pageCount: content.pageCount, page: content.page });
            });

            content.bind("resize", function () {
                that.trigger(REFRESH, { pageCount: content.pageCount, page: content.page });
            });

            content.bind(ITEM_CHANGE, function (e) {
                that.trigger(ITEM_CHANGE, e);

                that.angular("compile", function () {
                    return { elements: e.item, data: [{ dataItem: e.data}] };
                });
            });

            content.bind(CLEANUP, function (e) {
                that.angular("cleanup", function () {
                    return { elements: e.item };
                });
            });

            that._content = content;
            that.setDataSource(options.dataSource);

            var mobileContainer = that.container();

            if (mobileContainer.nullObject) {
                that.viewInit();
                that.viewShow();
            } else {
                mobileContainer.bind("show", proxy(this, "viewShow")).bind("init", proxy(this, "viewInit"));
            }
        },

        options: {
            name: "ScrollView",
            page: 0,
            duration: 400,
            velocityThreshold: 0.8,
            contentHeight: "auto",
            pageSize: 1,
            itemsPerPage: 1,
            bounceVelocityThreshold: 1.6,
            enablePager: true,
            autoBind: true,
            template: "",
            emptyTemplate: ""
        },

        events: [
            CHANGING,
            CHANGE,
            REFRESH
        ],

        destroy: function () {
            Widget.fn.destroy.call(this);
            kendo.destroy(this.element);
        },

        viewInit: function () {
            if (this.options.autoBind) {
                this._content.scrollTo(this._content.page, true);
            }
        },

        viewShow: function () {
            this.pane.refresh();
        },

        refresh: function () {
            var content = this._content;

            content.resizeTo(this.pane.size());
            this.page = content.page;
            this.trigger(REFRESH, { pageCount: content.pageCount, page: content.page });
        },

        content: function (html) {
            this.element.children().first().html(html);
            this._content._getPages();
            this.pane.refresh();
        },

        value: function (item) {
            var dataSource = this.dataSource;

            if (item) {
                this.scrollTo(dataSource.indexOf(item), true);
            } else {
                return dataSource.at(this.page);
            }
        },

        scrollTo: function (page, instant) {
            this._content.scrollTo(page, instant);
            this._syncWithContent();
        },

        prev: function () {
            var that = this,
                prevPage = that.page - 1;

            if (that._content instanceof VirtualScrollViewContent) {
                that._content.paneMoved(RIGHT_SWIPE, undefined, function (eventData) {
                    return that.trigger(CHANGING, eventData);
                });
            } else if (prevPage > -1) {
                that.scrollTo(prevPage);
            }
        },

        next: function () {
            var that = this,
                nextPage = that.page + 1;

            if (that._content instanceof VirtualScrollViewContent) {
                that._content.paneMoved(LEFT_SWIPE, undefined, function (eventData) {
                    return that.trigger(CHANGING, eventData);
                });
            } else if (nextPage < that._content.pageCount) {
                that.scrollTo(nextPage);
            }
        },

        setDataSource: function (dataSource) {
            if (!(this._content instanceof VirtualScrollViewContent)) {
                return;
            }
            // the scrollview should have a ready datasource for MVVM to function properly. But an empty datasource should not empty the element
            var emptyDataSource = !dataSource;
            this.dataSource = DataSource.create(dataSource);

            this._content.setDataSource(this.dataSource);

            if (this.options.autoBind && !emptyDataSource) {
                // this.items().remove();
                this.dataSource.fetch();
            }
        },

        items: function () {
            return this.element.find("." + VIRTUAL_PAGE_CLASS);
        },

        _syncWithContent: function () {
            var pages = this._content.pages,
                buffer = this._content.buffer,
                data,
                element;

            this.page = this._content.page;

            data = buffer ? buffer.at(this.page) : undefined;
            if (!(data instanceof Array)) {
                data = [data];
            }
            element = pages ? pages[1].element : undefined;

            this.trigger(CHANGE, { page: this.page, element: element, data: data });
        },

        _dragStart: function () {
            if (this._content.forcePageUpdate()) {
                this._syncWithContent();
            }
        },

        _dragEnd: function (e) {
            var that = this,
                velocity = e.x.velocity,
                velocityThreshold = this.options.velocityThreshold,
                swipeType = NUDGE,
                bounce = abs(velocity) > this.options.bounceVelocityThreshold;

            if (velocity > velocityThreshold) {
                swipeType = RIGHT_SWIPE;
            } else if (velocity < -velocityThreshold) {
                swipeType = LEFT_SWIPE;
            }

            this._content.paneMoved(swipeType, bounce, function (eventData) {
                return that.trigger(CHANGING, eventData);
            });
        },

        _transitionEnd: function () {
            if (this._content.updatePage()) {
                this._syncWithContent();
            }
        }
    });

    ui.plugin(ScrollView);

})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.mobile.ui,
        Widget = ui.Widget,
        support = kendo.support,
        CHANGE = "change",
        SWITCHON = "km-switch-on",
        SWITCHOFF = "km-switch-off",
        MARGINLEFT = "margin-left",
        ACTIVE_STATE = "km-state-active",
        DISABLED_STATE = "km-state-disabled",
        DISABLED = "disabled",
        TRANSFORMSTYLE = support.transitions.css + "transform",
        proxy = $.proxy;

    function limitValue(value, minLimit, maxLimit) {
        return Math.max(minLimit, Math.min(maxLimit, value));
    }

    var SWITCH_MARKUP = '<span class="km-switch km-widget">\
        <span class="km-switch-wrapper"><span class="km-switch-background"></span></span> \
        <span class="km-switch-container"><span class="km-switch-handle" > \
            <span class="km-switch-label-on">{0}</span> \
            <span class="km-switch-label-off">{1}</span> \
        </span> \
    </span>';

    var Switch = Widget.extend({
        init: function (element, options) {
            var that = this, checked;

            Widget.fn.init.call(that, element, options);

            options = that.options;

            that.wrapper = $(kendo.format(SWITCH_MARKUP, options.onLabel, options.offLabel));
            that.handle = that.wrapper.find(".km-switch-handle");
            that.background = that.wrapper.find(".km-switch-background");
            that.wrapper.insertBefore(that.element).prepend(that.element);

            that._drag();

            that.origin = parseInt(that.background.css(MARGINLEFT), 10);

            that.constrain = 0;
            that.snapPoint = 0;

            element = that.element[0];
            element.type = "checkbox";
            that._animateBackground = true;

            checked = that.options.checked;

            if (checked === null) {
                checked = element.checked;
            }

            that.check(checked);

            that.options.enable = that.options.enable && !that.element.attr(DISABLED);
            that.enable(that.options.enable);

            that.refresh();
            kendo.notify(that, kendo.mobile.ui);
        },

        refresh: function () {
            var that = this,
                handleWidth = that.handle.outerWidth(true);

            that.width = that.wrapper.width();

            that.constrain = that.width - handleWidth;
            that.snapPoint = that.constrain / 2;

            if (typeof that.origin != "number") {
                that.origin = parseInt(that.background.css(MARGINLEFT), 10);
            }

            that.background.data("origin", that.origin);

            that.check(that.element[0].checked);
        },

        events: [
            CHANGE
        ],

        options: {
            name: "Switch",
            onLabel: "on",
            offLabel: "off",
            checked: null,
            enable: true
        },

        check: function (check) {
            var that = this,
                element = that.element[0];

            if (check === undefined) {
                return element.checked;
            }

            that._position(check ? that.constrain : 0);
            element.checked = check;
            that.wrapper
                .toggleClass(SWITCHON, check)
                .toggleClass(SWITCHOFF, !check);
        },

        // alias for check, NG support
        value: function () {
            return this.check.apply(this, arguments);
        },

        destroy: function () {
            Widget.fn.destroy.call(this);
            this.userEvents.destroy();
        },

        toggle: function () {
            var that = this;

            that.check(!that.element[0].checked);
        },

        enable: function (enable) {
            var element = this.element,
                wrapper = this.wrapper;

            if (typeof enable == "undefined") {
                enable = true;
            }

            this.options.enable = enable;

            if (enable) {
                element.removeAttr(DISABLED);
            } else {
                element.attr(DISABLED, DISABLED);
            }

            wrapper.toggleClass(DISABLED_STATE, !enable);
        },

        _resize: function () {
            this.refresh();
        },

        _move: function (e) {
            var that = this;
            e.preventDefault();
            that._position(limitValue(that.position + e.x.delta, 0, that.width - that.handle.outerWidth(true)));
        },

        _position: function (position) {
            var that = this;

            that.position = position;
            that.handle.css(TRANSFORMSTYLE, "translatex(" + position + "px)");

            if (that._animateBackground) {
                that.background.css(MARGINLEFT, that.origin + position);
            }
        },

        _start: function () {
            if (!this.options.enable) {
                this.userEvents.cancel();
            } else {
                this.userEvents.capture();
                this.handle.addClass(ACTIVE_STATE);
            }
        },

        _stop: function () {
            var that = this;

            that.handle.removeClass(ACTIVE_STATE);
            that._toggle(that.position > that.snapPoint);
        },

        _toggle: function (checked) {
            var that = this,
                handle = that.handle,
                element = that.element[0],
                value = element.checked,
                duration = kendo.mobile.application && kendo.mobile.application.os.wp ? 100 : 200,
                distance;

            that.wrapper
                .toggleClass(SWITCHON, checked)
                .toggleClass(SWITCHOFF, !checked);

            that.position = distance = checked * that.constrain;

            if (that._animateBackground) {
                that.background
                    .kendoStop(true, true)
                    .kendoAnimate({ effects: "slideMargin", offset: distance, reset: true, reverse: !checked, axis: "left", duration: duration });
            }

            handle
                .kendoStop(true, true)
                .kendoAnimate({
                    effects: "slideTo",
                    duration: duration,
                    offset: distance + "px,0",
                    reset: true,
                    complete: function () {
                        if (value !== checked) {
                            element.checked = checked;
                            that.trigger(CHANGE, { checked: checked });
                        }
                    }
                });
        },

        _drag: function () {
            var that = this;

            that.userEvents = new kendo.UserEvents(that.wrapper, {
                tap: function () {
                    if (that.options.enable) {
                        that._toggle(!that.element[0].checked);
                    }
                },
                start: proxy(that._start, that),
                move: proxy(that._move, that),
                end: proxy(that._stop, that)
            });
        }
    });

    ui.plugin(Switch);
})(window.kendo.jQuery);





(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.mobile.ui,
        Widget = ui.Widget,
        ACTIVE_STATE_CLASS = "km-state-active",
        SELECT = "select";

    function createBadge(value) {
        return $('<span class="km-badge">' + value + '</span>');
    }

    var TabStrip = Widget.extend({
        init: function (element, options) {
            var that = this;

            Widget.fn.init.call(that, element, options);
            that.container().bind("show", $.proxy(this, "refresh"));

            that.element
               .addClass("km-tabstrip")
               .find("a").each(that._buildButton)
               .eq(that.options.selectedIndex).addClass(ACTIVE_STATE_CLASS);

            that.element.on("down", "a", "_release");
        },

        events: [
            SELECT
        ],

        switchTo: function (url) {
            var tabs = this.element.find('a'),
                tab,
                path,
                idx = 0,
                length = tabs.length;

            if (isNaN(url)) {
                for (; idx < length; idx++) {
                    tab = tabs[idx];
                    path = tab.href.replace(/(\#.+)(\?.+)$/, "$1"); // remove the fragment query string - http://www.foo.com?foo#bar**?baz=qux**

                    if (path.indexOf(url, path.length - url.length) !== -1) {
                        this._setActiveItem($(tab));
                        return true;
                    }
                }
            } else {
                this._setActiveItem(tabs.eq(url));
                return true;
            }

            return false;
        },

        switchByFullUrl: function (url) {
            var tab;

            tab = this.element.find("a[href$='" + url + "']");
            this._setActiveItem(tab);
        },

        clear: function () {
            this.currentItem().removeClass(ACTIVE_STATE_CLASS);
        },

        currentItem: function () {
            return this.element.children("." + ACTIVE_STATE_CLASS);
        },

        badge: function (item, value) {
            var tabstrip = this.element, badge;

            if (!isNaN(item)) {
                item = tabstrip.children().get(item);
            }

            item = tabstrip.find(item);
            badge = $(item.find(".km-badge")[0] || createBadge(value).insertAfter(item.children(".km-icon")));

            if (value || value === 0) {
                badge.html(value);
                return this;
            }

            if (value === false) {
                badge.empty().remove();
                return this;
            }

            return badge.html();
        },

        _release: function (e) {
            if (e.which > 1) {
                return;
            }

            var that = this,
                item = $(e.currentTarget);

            if (item[0] === that.currentItem()[0]) {
                return;
            }

            if (that.trigger(SELECT, { item: item })) {
                e.preventDefault();
            } else {
                that._setActiveItem(item);
            }
        },

        _setActiveItem: function (item) {
            if (!item[0]) {
                return;
            }
            this.clear();
            item.addClass(ACTIVE_STATE_CLASS);
        },

        _buildButton: function () {
            var button = $(this),
                icon = kendo.attrValue(button, "icon"),
                badge = kendo.attrValue(button, "badge"),
                image = button.find("img"),
                iconSpan = $('<span class="km-icon"/>');

            button
                .addClass("km-button")
                .attr(kendo.attr("role"), "tab")
                    .contents().not(image)
                    .wrapAll('<span class="km-text"/>');

            if (image[0]) {
                image.addClass("km-image").prependTo(button);
            } else {
                button.prepend(iconSpan);
                if (icon) {
                    iconSpan.addClass("km-" + icon);
                    if (badge || badge === 0) {
                        createBadge(badge).insertAfter(iconSpan);
                    }
                }
            }
        },

        refresh: function (e) {
            var url = e.view.element.attr(kendo.attr("url"));
            if (!this.switchTo(e.view.id) && url) {
                this.switchTo(url);
            }
        },

        options: {
            name: "TabStrip",
            selectedIndex: 0,
            enable: true
        }
    });

    ui.plugin(TabStrip);
})(window.kendo.jQuery);





return window.kendo;

}, typeof define == 'function' && define.amd ? define : function (_, f) { f(); });